From 684d1bcb3b0e12adee29cd3c83f99aca49cd201e Mon Sep 17 00:00:00 2001 From: James Monroe Date: Thu, 4 Apr 2013 17:35:38 -0500 Subject: [PATCH] Jedi Academy Release --- code/0_compiled_first/0_SH_Leak.cpp | 774 ++ code/0_compiled_first/vssver.scc | Bin 0 -> 48 bytes code/ALut.lib | Bin 0 -> 4702 bytes code/EaxMan.dll | Bin 0 -> 94208 bytes code/IFC22.dll | Bin 0 -> 200704 bytes code/JediAcademy.sln | 88 + code/JediAcademy.vssscc | 10 + 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/RMG/vssver.scc | Bin 0 -> 432 bytes 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/Ragl/vssver.scc | Bin 0 -> 112 bytes 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/Ratl/vssver.scc | Bin 0 -> 288 bytes 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/Ravl/vssver.scc | Bin 0 -> 112 bytes code/Rufl/hfile.cpp | 378 + code/Rufl/hfile.h | 61 + code/Rufl/hstring.cpp | 185 + code/Rufl/hstring.h | 106 + code/Rufl/random.cpp | 0 code/Rufl/random.h | 0 code/Rufl/vssver.scc | Bin 0 -> 128 bytes code/SHDebug/HA312W32.DLL | Bin 0 -> 382464 bytes code/SHDebug/SHW32.DLL | Bin 0 -> 112720 bytes code/SHDebug/vssver.scc | Bin 0 -> 64 bytes code/VU.bat | 1 + 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 | 2289 ++++ 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 | 1955 +++ code/cgame/cg_camera.h | 164 + code/cgame/cg_consolecmds.cpp | 324 + code/cgame/cg_credits.cpp | 654 + code/cgame/cg_draw.cpp | 4134 +++++++ code/cgame/cg_drawtools.cpp | 473 + code/cgame/cg_effects.cpp | 1087 ++ code/cgame/cg_ents.cpp | 2658 ++++ code/cgame/cg_event.cpp | 1279 ++ code/cgame/cg_headers.cpp | 3 + code/cgame/cg_headers.h | 20 + code/cgame/cg_info.cpp | 860 ++ 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 | 4517 +++++++ code/cgame/cg_marks.cpp | 264 + code/cgame/cg_media.h | 413 + code/cgame/cg_players.cpp | 8259 +++++++++++++ code/cgame/cg_playerstate.cpp | 364 + code/cgame/cg_predict.cpp | 780 ++ code/cgame/cg_public.h | 214 + code/cgame/cg_scoreboard.cpp | 373 + code/cgame/cg_servercmds.cpp | 254 + code/cgame/cg_snapshot.cpp | 407 + code/cgame/cg_syscalls.cpp | 646 + code/cgame/cg_text.cpp | 768 ++ code/cgame/cg_view.cpp | 2249 ++++ code/cgame/cg_weapons.cpp | 3309 +++++ code/cgame/common_headers.h | 10 + code/cgame/strip_objectives.h | 82 + code/cgame/vssver.scc | Bin 0 -> 944 bytes code/client/BinkVideo.cpp | 389 + code/client/BinkVideo.h | 63 + 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/OpenAL/vssver.scc | Bin 0 -> 128 bytes code/client/cl_cgame.cpp | 1312 ++ code/client/cl_cin.cpp | 1950 +++ code/client/cl_cin_console.cpp | 413 + code/client/cl_console.cpp | 727 ++ code/client/cl_input.cpp | 1089 ++ code/client/cl_input_hotswap.cpp | 246 + code/client/cl_input_hotswap.h | 64 + code/client/cl_keys.cpp | 1478 +++ code/client/cl_main.cpp | 1487 +++ 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 | 568 + code/client/cl_ui.cpp | 504 + code/client/client.h | 465 + code/client/client_ui.h | 13 + code/client/eax/EaxMan.h | 171 + code/client/eax/eax.h | 1562 +++ code/client/eax/vssver.scc | Bin 0 -> 64 bytes 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 | 3090 +++++ code/client/snd_local.h | 228 + code/client/snd_local_console.h | 128 + code/client/snd_mem.cpp | 1015 ++ code/client/snd_mem_console.cpp | 359 + code/client/snd_mix.cpp | 471 + code/client/snd_music.cpp | 1155 ++ code/client/snd_music.h | 67 + code/client/snd_public.h | 55 + code/client/vmachine.cpp | 39 + code/client/vmachine.h | 93 + code/client/vssver.scc | Bin 0 -> 608 bytes 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/IFC/vssver.scc | Bin 0 -> 480 bytes 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 | 248 + 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/ff/vssver.scc | Bin 0 -> 496 bytes 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 | 7609 ++++++++++++ code/game/AI_Mark1.cpp | 746 ++ code/game/AI_Mark2.cpp | 358 + code/game/AI_MineMonster.cpp | 269 + code/game/AI_Rancor.cpp | 1684 +++ code/game/AI_Remote.cpp | 389 + code/game/AI_RocketTrooper.cpp | 908 ++ 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 | 904 ++ code/game/AnimalNPC.c | 1065 ++ 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 | 4376 +++++++ code/game/NPC_stats.cpp | 4014 ++++++ code/game/NPC_utils.cpp | 1668 +++ code/game/Q3_Interface.cpp | 11277 +++++++++++++++++ code/game/Q3_Interface.h | 704 ++ code/game/SpeederNPC.c | 1180 ++ 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 | 1842 +++ code/game/bg_panimate.cpp | 6639 ++++++++++ code/game/bg_pmove.cpp | 15090 +++++++++++++++++++++++ code/game/bg_public.h | 736 ++ code/game/bg_slidemove.cpp | 573 + code/game/bg_vehicleLoad.c | 1668 +++ 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 | 5780 +++++++++ code/game/g_breakable.cpp | 1535 +++ code/game/g_camera.cpp | 256 + code/game/g_client.cpp | 2416 ++++ code/game/g_cmds.cpp | 1456 +++ code/game/g_combat.cpp | 6945 +++++++++++ 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 | 2141 ++++ code/game/g_mem.cpp | 38 + code/game/g_misc.cpp | 3297 +++++ code/game/g_misc_model.cpp | 792 ++ code/game/g_missile.cpp | 1534 +++ code/game/g_mover.cpp | 2657 ++++ code/game/g_nav.cpp | 407 + code/game/g_nav.h | 30 + code/game/g_navigator.cpp | 5508 +++++++++ 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 | 1272 ++ code/game/g_session.cpp | 221 + code/game/g_shared.h | 930 ++ code/game/g_spawn.cpp | 1655 +++ code/game/g_svcmds.cpp | 1342 ++ code/game/g_target.cpp | 1234 ++ code/game/g_trigger.cpp | 1685 +++ code/game/g_turret.cpp | 2516 ++++ code/game/g_usable.cpp | 251 + code/game/g_utils.cpp | 2149 ++++ code/game/g_vehicleLoad.cpp | 432 + code/game/g_vehicles.c | 3196 +++++ code/game/g_vehicles.h | 624 + code/game/g_weapon.cpp | 5357 ++++++++ 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/mssccprj.scc | 5 + 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 | 2419 ++++ code/game/say.h | 30 + code/game/statindex.h | 26 + code/game/surfaceflags.h | 190 + code/game/teams.h | 92 + code/game/vssver.scc | Bin 0 -> 2272 bytes code/game/weapons.h | 149 + code/game/wp_saber.cpp | 13660 ++++++++++++++++++++ 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/ghoul2/vssver.scc | Bin 0 -> 144 bytes code/goblib/goblib.cpp | 1876 +++ code/goblib/goblib.h | 299 + code/goblib/goblib.vcproj | 461 + code/goblib/goblib.vcproj.vspscc | 10 + code/goblib/mssccprj.scc | 5 + code/goblib/vssver.scc | Bin 0 -> 96 bytes 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/icarus/vssver.scc | Bin 0 -> 224 bytes 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/jpeg-6/vssver.scc | Bin 0 -> 784 bytes 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/mac/vssver.scc | Bin 0 -> 256 bytes 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/vssver.scc | Bin 0 -> 528 bytes code/mp3code/wavep.c | 96 + code/mssccprj.scc | 9 + 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/null/vssver.scc | Bin 0 -> 112 bytes code/png/png.cpp | 783 ++ code/png/png.h | 73 + code/png/vssver.scc | Bin 0 -> 64 bytes 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 | 1249 ++ 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 | 1244 ++ code/qcommon/cmd.cpp | 708 ++ code/qcommon/common.cpp | 1580 +++ code/qcommon/cvar.cpp | 951 ++ code/qcommon/files.h | 118 + code/qcommon/files_common.cpp | 588 + code/qcommon/files_console.cpp | 1033 ++ code/qcommon/files_pc.cpp | 1741 +++ code/qcommon/fixedmap.h | 149 + 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 | 850 ++ code/qcommon/qfiles.h | 633 + code/qcommon/sparc.h | 725 ++ code/qcommon/sstring.h | 120 + code/qcommon/stringed_ingame.cpp | 1264 ++ code/qcommon/stringed_ingame.h | 110 + code/qcommon/stringed_interface.cpp | 215 + code/qcommon/stringed_interface.h | 21 + code/qcommon/stv_version.h | 13 + code/qcommon/tags.h | 54 + code/qcommon/timing.h | 62 + code/qcommon/tri_coll_test.cpp | 506 + code/qcommon/unzip.cpp | 1348 ++ code/qcommon/unzip.h | 286 + code/qcommon/vssver.scc | Bin 0 -> 848 bytes code/qcommon/z_memman_console.cpp | 1843 +++ code/qcommon/z_memman_pc.cpp | 958 ++ 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 | 2283 ++++ code/renderer/tr_WorldEffects.h | 43 + code/renderer/tr_animation.cpp | 478 + code/renderer/tr_arioche.cpp | 149 + code/renderer/tr_backend.cpp | 1963 +++ code/renderer/tr_bsp.cpp | 1458 +++ code/renderer/tr_bsp_xbox.cpp | 1666 +++ code/renderer/tr_cmds.cpp | 512 + code/renderer/tr_curve.cpp | 903 ++ code/renderer/tr_draw.cpp | 1124 ++ code/renderer/tr_flares.cpp | 427 + code/renderer/tr_font.cpp | 1714 +++ code/renderer/tr_font.h | 34 + code/renderer/tr_ghoul2.cpp | 4374 +++++++ code/renderer/tr_image.cpp | 3373 +++++ code/renderer/tr_init.cpp | 1626 +++ 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_local.h | 2170 ++++ code/renderer/tr_main.cpp | 1726 +++ code/renderer/tr_marks.cpp | 500 + code/renderer/tr_mesh.cpp | 437 + code/renderer/tr_model.cpp | 1197 ++ 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 | 402 + code/renderer/tr_shade.cpp | 2776 +++++ code/renderer/tr_shade_calc.cpp | 1587 +++ code/renderer/tr_shader.cpp | 4122 +++++++ code/renderer/tr_shadows.cpp | 809 ++ code/renderer/tr_sky.cpp | 845 ++ code/renderer/tr_stl.cpp | 76 + code/renderer/tr_stl.h | 31 + code/renderer/tr_surface.cpp | 2383 ++++ code/renderer/tr_surfacesprites.cpp | 1492 +++ code/renderer/tr_terrain.cpp | 1047 ++ code/renderer/tr_types.h | 240 + code/renderer/tr_world.cpp | 1021 ++ code/renderer/vssver.scc | Bin 0 -> 864 bytes 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 | 715 ++ code/server/sv_init.cpp | 596 + code/server/sv_main.cpp | 572 + code/server/sv_savegame.cpp | 2002 +++ code/server/sv_snapshot.cpp | 749 ++ code/server/sv_world.cpp | 1012 ++ code/server/vssver.scc | Bin 0 -> 208 bytes 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/smartheap/vssver.scc | Bin 0 -> 112 bytes code/starwars.vcproj | 6784 ++++++++++ code/starwars.vcproj.vspscc | 10 + code/tonet.bat | 10 + code/tosend.bat | 6 + code/ui/gameinfo.cpp | 35 + code/ui/gameinfo.h | 24 + code/ui/menudef.h | 138 + code/ui/ui.def | 4 + code/ui/ui_atoms.cpp | 480 + code/ui/ui_connect.cpp | 95 + code/ui/ui_debug.cpp | 747 ++ code/ui/ui_local.h | 219 + code/ui/ui_main.cpp | 6314 ++++++++++ code/ui/ui_public.h | 250 + code/ui/ui_saber.cpp | 861 ++ code/ui/ui_shared.cpp | 11977 ++++++++++++++++++ code/ui/ui_shared.h | 528 + code/ui/ui_splash.cpp | 290 + code/ui/ui_splash.h | 11 + code/ui/ui_syscalls.cpp | 177 + code/ui/vssver.scc | Bin 0 -> 288 bytes 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/unix/vssver.scc | Bin 0 -> 272 bytes code/update_spents.bat | 1 + code/vssver.scc | Bin 0 -> 240 bytes 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/FeelIt/vssver.scc | Bin 0 -> 528 bytes 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 | 180 + code/win32/rad.h | 962 ++ code/win32/resource.h | 21 + code/win32/snd_fx_img.h | 85 + code/win32/starwars.ico | Bin 0 -> 3638 bytes code/win32/vssver.scc | Bin 0 -> 704 bytes code/win32/win_file.h | 33 + code/win32/win_file_xbox.cpp | 173 + code/win32/win_filecode.cpp | 346 + 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_input.cpp | 1147 ++ code/win32/win_input.h | 101 + code/win32/win_input_console.cpp | 523 + code/win32/win_input_rumble.cpp | 707 ++ code/win32/win_input_xbox.cpp | 306 + code/win32/win_local.h | 76 + code/win32/win_main.cpp | 1241 ++ code/win32/win_main_common.cpp | 332 + code/win32/win_main_console.cpp | 584 + code/win32/win_qal_xbox.cpp | 1301 ++ code/win32/win_qgl.cpp | 4276 +++++++ code/win32/win_qgl_dx8.cpp | 6680 ++++++++++ code/win32/win_shared.cpp | 281 + code/win32/win_snd.cpp | 414 + code/win32/win_stencilshadow.cpp | 437 + code/win32/win_stencilshadow.h | 45 + code/win32/win_stream_dx8.cpp | 290 + code/win32/win_syscon.cpp | 536 + code/win32/win_video.cpp | 325 + code/win32/win_wndproc.cpp | 532 + code/win32/winquake.rc | 101 + code/x_exe/mssccprj.scc | 5 + code/x_exe/vssver.scc | Bin 0 -> 48 bytes code/x_exe/x_exe.vcproj | 1062 ++ code/x_game/mssccprj.scc | 5 + code/x_game/vssver.scc | Bin 0 -> 48 bytes code/x_game/x_game.vcproj | 1019 ++ code/zlib32/deflate.cpp | 2078 ++++ code/zlib32/deflate.h | 231 + code/zlib32/inflate.cpp | 1839 +++ code/zlib32/inflate.h | 145 + code/zlib32/vssver.scc | Bin 0 -> 128 bytes code/zlib32/zip.h | 195 + code/zlib32/zipcommon.cpp | 117 + codemp/ALut.lib | Bin 0 -> 4702 bytes codemp/CommandLine.txt | 3 + codemp/Debug/HA312W32.DLL | Bin 0 -> 382464 bytes codemp/Debug/SHW32.DLL | Bin 0 -> 112720 bytes codemp/Debug/vssver.scc | Bin 0 -> 64 bytes codemp/EaxMan.dll | Bin 0 -> 94208 bytes codemp/JKA_mp.sln | 176 + codemp/JKA_mp.vssscc | 10 + codemp/OpenAL32.dll | Bin 0 -> 221184 bytes codemp/OpenAL32.lib | Bin 0 -> 16866 bytes 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_InstanceFile.cpp | 201 + codemp/RMG/RM_InstanceFile.h | 28 + 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_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/RMG/vssver.scc | Bin 0 -> 432 bytes codemp/Ratl/bits_vs.h | 218 + codemp/Ratl/ratl_common.h | 1180 ++ codemp/Ratl/vector_vs.h | 757 ++ codemp/Ratl/vssver.scc | Bin 0 -> 80 bytes codemp/Ravl/CVec.h | 1002 ++ codemp/Ravl/vssver.scc | Bin 0 -> 48 bytes 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/mssccprj.scc | 5 + 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/Splines/vssver.scc | Bin 0 -> 304 bytes codemp/VU.bat | 1 + codemp/WinDed.dsp | 1123 ++ codemp/WinDed.vcproj | 901 ++ codemp/WinDed.vcproj.vspscc | 10 + 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 | 760 ++ 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 | 1084 ++ 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 | 4525 +++++++ 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 | 3593 ++++++ 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 | 519 + codemp/botlib/be_interface.cpp | 869 ++ codemp/botlib/be_interface.h | 40 + codemp/botlib/botlib.vcproj | 434 + codemp/botlib/botlib.vcproj.vspscc | 10 + 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/botlib/mssccprj.scc | 5 + codemp/botlib/vssver.scc | Bin 0 -> 912 bytes codemp/buildvms.bat | 8 + codemp/cgame/JK2_cgame.def | 3 + codemp/cgame/JK2_cgame.vcproj | 563 + codemp/cgame/JK2_cgame.vcproj.vspscc | 10 + codemp/cgame/animtable.h | 1792 +++ codemp/cgame/asm2mak.cfg | 36 + codemp/cgame/cg_consolecmds.c | 391 + codemp/cgame/cg_draw.c | 8468 +++++++++++++ codemp/cgame/cg_drawtools.c | 690 ++ codemp/cgame/cg_effects.c | 1546 +++ codemp/cgame/cg_ents.c | 3783 ++++++ codemp/cgame/cg_event.c | 3432 ++++++ codemp/cgame/cg_info.c | 461 + codemp/cgame/cg_light.c | 85 + codemp/cgame/cg_lights.h | 16 + codemp/cgame/cg_local.h | 2611 ++++ codemp/cgame/cg_localents.c | 869 ++ codemp/cgame/cg_main.c | 4160 +++++++ codemp/cgame/cg_marks.c | 2272 ++++ codemp/cgame/cg_media.h | 0 codemp/cgame/cg_newDraw.c | 899 ++ codemp/cgame/cg_playeranimate.c | 0 codemp/cgame/cg_players.c | 10948 ++++++++++++++++ codemp/cgame/cg_playerstate.c | 535 + codemp/cgame/cg_predict.c | 1512 +++ codemp/cgame/cg_public.h | 594 + codemp/cgame/cg_saga.c | 1094 ++ codemp/cgame/cg_scoreboard.c | 639 + codemp/cgame/cg_servercmds.c | 1687 +++ codemp/cgame/cg_snapshot.c | 414 + codemp/cgame/cg_strap.c | 73 + codemp/cgame/cg_syscalls.c | 1106 ++ codemp/cgame/cg_turret.c | 242 + codemp/cgame/cg_view.c | 2724 ++++ codemp/cgame/cg_weaponinit.c | 592 + codemp/cgame/cg_weapons.c | 2582 ++++ codemp/cgame/cgame.bat | 19 + codemp/cgame/cgame.q3asm | 48 + 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 | 155 + codemp/cgame/fx_local.h | 63 + codemp/cgame/fx_rocketlauncher.c | 61 + codemp/cgame/holocronicons.h | 24 + codemp/cgame/mssccprj.scc | 5 + codemp/cgame/tr_types.h | 339 + codemp/cgame/vssver.scc | Bin 0 -> 816 bytes codemp/cleanvms.bat | 11 + codemp/client/0_SH_Leak.cpp | 412 + codemp/client/BinkVideo.cpp | 524 + codemp/client/BinkVideo.h | 73 + codemp/client/FXExport.cpp | 105 + codemp/client/FXExport.h | 23 + codemp/client/FxPrimitives.cpp | 2344 ++++ codemp/client/FxPrimitives.h | 611 + codemp/client/FxScheduler.cpp | 1750 +++ codemp/client/FxScheduler.h | 505 + codemp/client/FxSystem.cpp | 130 + codemp/client/FxSystem.h | 223 + codemp/client/FxTemplate.cpp | 2386 ++++ codemp/client/FxUtil.cpp | 1232 ++ codemp/client/FxUtil.h | 116 + 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/OpenAL/vssver.scc | Bin 0 -> 128 bytes codemp/client/cl_cgame.cpp | 2093 ++++ codemp/client/cl_cin.cpp | 1494 +++ codemp/client/cl_cin_console.cpp | 332 + codemp/client/cl_console.cpp | 831 ++ codemp/client/cl_input.cpp | 1892 +++ codemp/client/cl_keys.cpp | 1708 +++ codemp/client/cl_main.cpp | 3679 ++++++ codemp/client/cl_net_chan.cpp | 175 + codemp/client/cl_parse.cpp | 1033 ++ codemp/client/cl_scrn.cpp | 624 + codemp/client/cl_ui.cpp | 1518 +++ codemp/client/client.h | 622 + codemp/client/eax/EaxMan.h | 171 + codemp/client/eax/eax.h | 1562 +++ codemp/client/eax/vssver.scc | Bin 0 -> 64 bytes codemp/client/fffx.h | 129 + codemp/client/keycodes.h | 347 + codemp/client/keys.h | 66 + codemp/client/snd_ambient.cpp | 1137 ++ codemp/client/snd_ambient.h | 118 + codemp/client/snd_dma.cpp | 6322 ++++++++++ codemp/client/snd_dma_console.cpp | 2933 +++++ codemp/client/snd_local.h | 228 + codemp/client/snd_local_console.h | 121 + codemp/client/snd_mem.cpp | 1015 ++ codemp/client/snd_mem_console.cpp | 355 + 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 | 63 + codemp/client/vssver.scc | Bin 0 -> 720 bytes codemp/encryption/encryption.h | 6 + codemp/encryption/vssver.scc | Bin 0 -> 48 bytes codemp/ff/ff_console.cpp | 248 + codemp/ff/vssver.scc | Bin 0 -> 48 bytes codemp/game/AnimalNPC.c | 946 ++ codemp/game/FighterNPC.c | 1961 +++ codemp/game/JK2_game.def | 3 + codemp/game/JK2_game.vcproj | 744 ++ codemp/game/JK2_game.vcproj.vspscc | 10 + codemp/game/NPC.c | 2110 ++++ codemp/game/NPC_AI_Atst.c | 328 + codemp/game/NPC_AI_Default.c | 957 ++ codemp/game/NPC_AI_Droid.c | 621 + codemp/game/NPC_AI_GalakMech.c | 1297 ++ 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_Interrogator.c | 467 + codemp/game/NPC_AI_Jedi.c | 6168 +++++++++ codemp/game/NPC_AI_Mark1.c | 764 ++ codemp/game/NPC_AI_Mark2.c | 362 + codemp/game/NPC_AI_MineMonster.c | 278 + codemp/game/NPC_AI_Rancor.c | 955 ++ 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 | 1125 ++ codemp/game/NPC_senses.c | 934 ++ codemp/game/NPC_sounds.c | 93 + codemp/game/NPC_spawn.c | 4239 +++++++ codemp/game/NPC_stats.c | 3307 +++++ codemp/game/NPC_utils.c | 1782 +++ codemp/game/SpeederNPC.c | 1134 ++ codemp/game/WalkerNPC.c | 636 + codemp/game/ai.h | 126 + codemp/game/ai_main.c | 7642 ++++++++++++ codemp/game/ai_main.h | 411 + codemp/game/ai_util.c | 867 ++ codemp/game/ai_wpnav.c | 3813 ++++++ codemp/game/anims.h | 1797 +++ codemp/game/asm2mak.cfg | 34 + 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 | 3404 +++++ codemp/game/bg_panimate.c | 2957 +++++ codemp/game/bg_pmove.c | 10868 ++++++++++++++++ codemp/game/bg_public.h | 1670 +++ codemp/game/bg_saber.c | 3687 ++++++ codemp/game/bg_saberLoad.c | 1164 ++ codemp/game/bg_saga.c | 1507 +++ codemp/game/bg_saga.h | 115 + codemp/game/bg_slidemove.c | 1071 ++ codemp/game/bg_strap.h | 38 + codemp/game/bg_vehicleLoad.c | 1668 +++ 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 | 5794 +++++++++ codemp/game/g_ICARUScb.h | 15 + codemp/game/g_active.c | 3765 ++++++ codemp/game/g_arenas.c | 343 + codemp/game/g_bot.c | 1308 ++ codemp/game/g_client.c | 3809 ++++++ codemp/game/g_cmds.c | 3911 ++++++ codemp/game/g_combat.c | 5617 +++++++++ codemp/game/g_exphysics.c | 232 + codemp/game/g_headers.h | 4 + codemp/game/g_items.c | 3243 +++++ codemp/game/g_local.h | 1969 +++ codemp/game/g_log.c | 1751 +++ codemp/game/g_main.c | 4065 ++++++ codemp/game/g_mem.c | 41 + codemp/game/g_misc.c | 3494 ++++++ codemp/game/g_missile.c | 1010 ++ codemp/game/g_mover.c | 3265 +++++ codemp/game/g_nav.c | 1913 +++ codemp/game/g_nav.h | 78 + codemp/game/g_navnew.c | 865 ++ codemp/game/g_object.c | 287 + codemp/game/g_public.h | 920 ++ codemp/game/g_saga.c | 1884 +++ codemp/game/g_session.c | 322 + codemp/game/g_spawn.c | 1472 +++ codemp/game/g_strap.c | 73 + codemp/game/g_svcmds.c | 470 + codemp/game/g_syscalls.c | 1469 +++ 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 | 1791 +++ codemp/game/g_turret.c | 849 ++ codemp/game/g_turret_G2.c | 1277 ++ codemp/game/g_utils.c | 2311 ++++ codemp/game/g_vehicleTurret.c | 435 + codemp/game/g_vehicles.c | 3334 +++++ codemp/game/g_weapon.c | 4953 ++++++++ codemp/game/game.bat | 19 + codemp/game/game.q3asm | 80 + codemp/game/inv.h | 104 + codemp/game/match.h | 122 + codemp/game/mssccprj.scc | 5 + codemp/game/npc_headers.h | 7 + codemp/game/q_math.c | 1663 +++ codemp/game/q_shared.c | 1378 +++ codemp/game/q_shared.h | 3055 +++++ 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/vssver.scc | Bin 0 -> 2144 bytes codemp/game/w_force.c | 5787 +++++++++ codemp/game/w_saber.c | 8957 ++++++++++++++ codemp/game/w_saber.h | 74 + codemp/ghoul2/G2.h | 40 + codemp/ghoul2/G2_API.cpp | 2654 ++++ codemp/ghoul2/G2_bolts.cpp | 331 + codemp/ghoul2/G2_bones.cpp | 4905 ++++++++ codemp/ghoul2/G2_gore.h | 201 + codemp/ghoul2/G2_local.h | 223 + codemp/ghoul2/G2_misc.cpp | 1926 +++ codemp/ghoul2/G2_surfaces.cpp | 677 + codemp/ghoul2/ghoul2_shared.h | 472 + codemp/ghoul2/vssver.scc | Bin 0 -> 176 bytes codemp/goblib/goblib.cpp | 1876 +++ codemp/goblib/goblib.h | 299 + codemp/goblib/goblib.vcproj | 339 + codemp/goblib/mssccprj.scc | 5 + codemp/goblib/vssver.scc | Bin 0 -> 80 bytes codemp/icarus/BlockStream.cpp | 698 ++ codemp/icarus/GameInterface.cpp | 733 ++ codemp/icarus/GameInterface.h | 36 + codemp/icarus/Instance.cpp | 655 + codemp/icarus/Interface.cpp | 24 + codemp/icarus/Interpreter.cpp | 2506 ++++ codemp/icarus/Memory.cpp | 20 + 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/Sequencer.cpp | 2483 ++++ codemp/icarus/TaskManager.cpp | 1994 +++ codemp/icarus/Tokenizer.cpp | 2837 +++++ codemp/icarus/blockstream.h | 198 + codemp/icarus/icarus.h | 32 + codemp/icarus/instance.h | 81 + codemp/icarus/interface.h | 72 + codemp/icarus/interpreter.h | 224 + codemp/icarus/module.h | 1 + codemp/icarus/sequence.h | 98 + codemp/icarus/sequencer.h | 189 + codemp/icarus/taskmanager.h | 191 + codemp/icarus/tokenizer.h | 601 + codemp/icarus/vssver.scc | Bin 0 -> 432 bytes codemp/install.bat | 1 + codemp/installvms.bat | 4 + codemp/jk2mp.vcproj | 6986 +++++++++++ codemp/jk2mp.vcproj.vspscc | 10 + 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/jpeg-6/vssver.scc | Bin 0 -> 784 bytes 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/vssver.scc | Bin 0 -> 528 bytes codemp/mp3code/wavep.c | 96 + codemp/mssccprj.scc | 17 + 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/vssver.scc | Bin 0 -> 176 bytes codemp/null/win_main.cpp | 1482 +++ codemp/png/png.cpp | 783 ++ codemp/png/png.h | 73 + codemp/png/vssver.scc | Bin 0 -> 64 bytes codemp/qcommon/CNetProfile.cpp | 97 + codemp/qcommon/GenericParser2.cpp | 1203 ++ codemp/qcommon/GenericParser2.h | 204 + codemp/qcommon/INetProfile.h | 20 + codemp/qcommon/MiniHeap.h | 57 + codemp/qcommon/RoffSystem.cpp | 1040 ++ codemp/qcommon/RoffSystem.h | 185 + 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 | 1127 ++ codemp/qcommon/cm_local.h | 310 + codemp/qcommon/cm_patch.cpp | 1809 +++ codemp/qcommon/cm_patch.h | 128 + codemp/qcommon/cm_patch_xbox.cpp | 1782 +++ 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 | 538 + codemp/qcommon/cm_terrain.cpp | 1720 +++ codemp/qcommon/cm_terrainmap.cpp | 497 + codemp/qcommon/cm_terrainmap.h | 83 + codemp/qcommon/cm_test.cpp | 573 + codemp/qcommon/cm_trace.cpp | 1992 +++ codemp/qcommon/cmd_common.cpp | 508 + codemp/qcommon/cmd_console.cpp | 165 + codemp/qcommon/cmd_pc.cpp | 174 + codemp/qcommon/common.cpp | 2236 ++++ codemp/qcommon/cvar.cpp | 1019 ++ codemp/qcommon/disablewarnings.h | 38 + codemp/qcommon/exe_headers.cpp | 3 + codemp/qcommon/exe_headers.h | 5 + codemp/qcommon/files.cpp | 3614 ++++++ codemp/qcommon/files.h | 158 + codemp/qcommon/files_common.cpp | 512 + codemp/qcommon/files_console.cpp | 1031 ++ codemp/qcommon/files_pc.cpp | 3125 +++++ codemp/qcommon/fixedmap.h | 148 + codemp/qcommon/game_version.h | 14 + codemp/qcommon/hstring.cpp | 501 + codemp/qcommon/hstring.h | 229 + codemp/qcommon/huffman.cpp | 417 + codemp/qcommon/md4.cpp | 296 + codemp/qcommon/msg.cpp | 3318 +++++ codemp/qcommon/net_chan.cpp | 657 + codemp/qcommon/platform.h | 22 + codemp/qcommon/q_math.cpp | 4 + codemp/qcommon/q_shared.cpp | 4 + codemp/qcommon/qcommon.h | 1132 ++ codemp/qcommon/qfiles.h | 606 + codemp/qcommon/sparc.h | 725 ++ codemp/qcommon/sstring.h | 120 + codemp/qcommon/stringed_ingame.cpp | 1264 ++ codemp/qcommon/stringed_ingame.h | 110 + codemp/qcommon/stringed_interface.cpp | 215 + codemp/qcommon/stringed_interface.h | 21 + codemp/qcommon/tags.h | 75 + 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/vssver.scc | Bin 0 -> 1184 bytes codemp/qcommon/z_memman_console.cpp | 1825 +++ codemp/qcommon/z_memman_pc.cpp | 832 ++ 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/qgl.h | 757 ++ codemp/renderer/qgl_console.h | 1205 ++ codemp/renderer/tr_WorldEffects.cpp | 2025 +++ codemp/renderer/tr_WorldEffects.h | 108 + codemp/renderer/tr_animation.cpp | 16 + codemp/renderer/tr_arioche.cpp | 117 + codemp/renderer/tr_backend.cpp | 2328 ++++ codemp/renderer/tr_bsp.cpp | 2123 ++++ codemp/renderer/tr_bsp_xbox.cpp | 1765 +++ codemp/renderer/tr_cmds.cpp | 476 + codemp/renderer/tr_curve.cpp | 612 + codemp/renderer/tr_curve_xbox.cpp | 536 + codemp/renderer/tr_flares.cpp | 428 + codemp/renderer/tr_font.cpp | 1714 +++ codemp/renderer/tr_font.h | 34 + codemp/renderer/tr_ghoul2.cpp | 5509 +++++++++ codemp/renderer/tr_image.cpp | 3365 +++++ codemp/renderer/tr_image_xbox.cpp | 2579 ++++ codemp/renderer/tr_init.cpp | 1522 +++ codemp/renderer/tr_landscape.h | 191 + codemp/renderer/tr_light.cpp | 483 + codemp/renderer/tr_local.h | 2365 ++++ codemp/renderer/tr_main.cpp | 1631 +++ codemp/renderer/tr_marks.cpp | 449 + codemp/renderer/tr_mesh.cpp | 423 + codemp/renderer/tr_model.cpp | 1838 +++ codemp/renderer/tr_noise.cpp | 84 + codemp/renderer/tr_public.h | 118 + codemp/renderer/tr_quicksprite.cpp | 222 + codemp/renderer/tr_quicksprite.h | 47 + codemp/renderer/tr_scene.cpp | 886 ++ codemp/renderer/tr_shade.cpp | 2475 ++++ codemp/renderer/tr_shade_calc.cpp | 1671 +++ codemp/renderer/tr_shader.cpp | 4283 +++++++ codemp/renderer/tr_shadows.cpp | 709 ++ codemp/renderer/tr_sky.cpp | 849 ++ codemp/renderer/tr_surface.cpp | 2033 +++ codemp/renderer/tr_surfacesprites.cpp | 1463 +++ codemp/renderer/tr_terrain.cpp | 1056 ++ codemp/renderer/tr_world.cpp | 1959 +++ codemp/renderer/vssver.scc | Bin 0 -> 752 bytes codemp/server/NPCNav/gameCallbacks.cpp | 49 + codemp/server/NPCNav/navigator.cpp | 2783 +++++ codemp/server/NPCNav/navigator.h | 280 + codemp/server/NPCNav/vssver.scc | Bin 0 -> 80 bytes codemp/server/exe_headers.h | 13 + codemp/server/server.h | 437 + codemp/server/sv_bot.cpp | 797 ++ codemp/server/sv_ccmds.cpp | 1019 ++ codemp/server/sv_client.cpp | 1855 +++ codemp/server/sv_game.cpp | 1758 +++ codemp/server/sv_init.cpp | 988 ++ codemp/server/sv_main.cpp | 937 ++ codemp/server/sv_net_chan.cpp | 169 + codemp/server/sv_snapshot.cpp | 833 ++ codemp/server/sv_world.cpp | 894 ++ codemp/server/vssver.scc | Bin 0 -> 208 bytes codemp/smartheap/HA312W32.DLL | Bin 0 -> 382464 bytes codemp/smartheap/HAW32M.LIB | Bin 0 -> 212108 bytes codemp/smartheap/HEAPAGNT.H | 442 + codemp/smartheap/SHW32.DLL | Bin 0 -> 112720 bytes codemp/smartheap/SMRTHEAP.C | 54 + codemp/smartheap/SMRTHEAP.H | 847 ++ codemp/smartheap/smrtheap.hpp | 197 + codemp/smartheap/vssver.scc | Bin 0 -> 144 bytes codemp/strings/str_server.h | 23 + codemp/strings/vssver.scc | Bin 0 -> 48 bytes codemp/tonet.bat | 30 + codemp/tosend.bat | 5 + codemp/ui/asm2mak.cfg | 34 + codemp/ui/keycodes.h | 349 + codemp/ui/mssccprj.scc | 5 + codemp/ui/ui.bat | 19 + codemp/ui/ui.def | 3 + codemp/ui/ui.q3asm | 14 + codemp/ui/ui.vcproj | 433 + codemp/ui/ui.vcproj.vspscc | 10 + codemp/ui/ui_atoms.c | 493 + codemp/ui/ui_force.c | 1345 ++ codemp/ui/ui_force.h | 33 + codemp/ui/ui_gameinfo.c | 333 + codemp/ui/ui_local.h | 1166 ++ codemp/ui/ui_main.c | 11735 ++++++++++++++++++ codemp/ui/ui_players.c | 1338 ++ codemp/ui/ui_public.h | 252 + codemp/ui/ui_saber.c | 1107 ++ codemp/ui/ui_shared.c | 10157 +++++++++++++++ codemp/ui/ui_shared.h | 622 + codemp/ui/ui_syscalls.c | 651 + codemp/ui/ui_util.c | 11 + codemp/ui/vssver.scc | Bin 0 -> 352 bytes codemp/unix/files_linux.cpp | 3071 +++++ 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/makefile | 1505 +++ codemp/unix/snapvector.nasm | 75 + codemp/unix/unix_main.c | 1164 ++ codemp/unix/unix_net.c | 599 + codemp/unix/unix_shared.cpp | 350 + codemp/unix/vm_x86.c | 8 + codemp/unix/vssver.scc | Bin 0 -> 256 bytes codemp/update_MPents.bat | 1 + codemp/vssver.scc | Bin 0 -> 384 bytes 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 | 180 + codemp/win32/qe3.ico | Bin 0 -> 3638 bytes codemp/win32/resource.h | 23 + codemp/win32/snd_fx_img.h | 85 + codemp/win32/ui.rc | 104 + codemp/win32/vssver.scc | Bin 0 -> 640 bytes 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 | 281 + codemp/win32/win_input.cpp | 1141 ++ codemp/win32/win_input.h | 101 + codemp/win32/win_input_console.cpp | 648 + codemp/win32/win_input_rumble.cpp | 705 ++ codemp/win32/win_input_xbox.cpp | 309 + codemp/win32/win_local.h | 84 + codemp/win32/win_main.cpp | 1609 +++ codemp/win32/win_main_common.cpp | 331 + codemp/win32/win_main_console.cpp | 615 + codemp/win32/win_net.cpp | 1222 ++ codemp/win32/win_qal_xbox.cpp | 1321 ++ codemp/win32/win_qgl.cpp | 4271 +++++++ codemp/win32/win_qgl_dx8.cpp | 6495 ++++++++++ codemp/win32/win_shared.cpp | 547 + codemp/win32/win_snd.cpp | 382 + codemp/win32/win_stream_dx8.cpp | 290 + codemp/win32/win_syscon.cpp | 574 + codemp/win32/win_wndproc.cpp | 547 + codemp/win32/winquake.rc | 101 + codemp/x_botlib/mssccprj.scc | 5 + codemp/x_botlib/vssver.scc | Bin 0 -> 48 bytes codemp/x_botlib/x_botlib.vcproj | 470 + codemp/x_exe/mssccprj.scc | 5 + codemp/x_exe/vssver.scc | Bin 0 -> 48 bytes codemp/x_exe/x_exe.vcproj | 1144 ++ codemp/x_jk2cgame/mssccprj.scc | 5 + codemp/x_jk2cgame/vssver.scc | Bin 0 -> 48 bytes codemp/x_jk2cgame/x_jk2cgame.vcproj | 977 ++ codemp/x_jk2game/mssccprj.scc | 5 + codemp/x_jk2game/vssver.scc | Bin 0 -> 48 bytes codemp/x_jk2game/x_jk2game.vcproj | 1648 +++ codemp/x_ui/mssccprj.scc | 5 + codemp/x_ui/vssver.scc | Bin 0 -> 48 bytes codemp/x_ui/x_ui.vcproj | 361 + codemp/zlib32/deflate.cpp | 2078 ++++ codemp/zlib32/deflate.h | 231 + codemp/zlib32/inflate.cpp | 1839 +++ codemp/zlib32/inflate.h | 145 + codemp/zlib32/vssver.scc | Bin 0 -> 128 bytes codemp/zlib32/zip.h | 195 + codemp/zlib32/zipcommon.cpp | 117 + ui/menudef.h | 388 + ui/vssver.scc | Bin 0 -> 48 bytes 1520 files changed, 1152378 insertions(+) create mode 100644 code/0_compiled_first/0_SH_Leak.cpp create mode 100644 code/0_compiled_first/vssver.scc create mode 100644 code/ALut.lib create mode 100644 code/EaxMan.dll create mode 100644 code/IFC22.dll create mode 100644 code/JediAcademy.sln create mode 100644 code/JediAcademy.vssscc 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/RMG/vssver.scc 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/Ragl/vssver.scc 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/Ratl/vssver.scc 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/Ravl/vssver.scc 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/Rufl/vssver.scc create mode 100644 code/SHDebug/HA312W32.DLL create mode 100644 code/SHDebug/SHW32.DLL create mode 100644 code/SHDebug/vssver.scc create mode 100644 code/VU.bat 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/cgame/vssver.scc 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/OpenAL/vssver.scc 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/eax/vssver.scc 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/client/vssver.scc 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/IFC/vssver.scc 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/ff/vssver.scc 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/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/mssccprj.scc 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/vssver.scc 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/ghoul2/vssver.scc 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.vspscc create mode 100644 code/goblib/mssccprj.scc create mode 100644 code/goblib/vssver.scc 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/icarus/vssver.scc 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/jpeg-6/vssver.scc 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/mac/vssver.scc 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/vssver.scc create mode 100644 code/mp3code/wavep.c create mode 100644 code/mssccprj.scc 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/null/vssver.scc create mode 100644 code/png/png.cpp create mode 100644 code/png/png.h create mode 100644 code/png/vssver.scc 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/vssver.scc create mode 100644 code/qcommon/z_memman_console.cpp create mode 100644 code/qcommon/z_memman_pc.cpp 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_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/renderer/vssver.scc 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/server/vssver.scc 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/smartheap/vssver.scc create mode 100644 code/starwars.vcproj create mode 100644 code/starwars.vcproj.vspscc 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_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/ui/vssver.scc 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/unix/vssver.scc create mode 100644 code/update_spents.bat create mode 100644 code/vssver.scc 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/FeelIt/vssver.scc 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/snd_fx_img.h create mode 100644 code/win32/starwars.ico create mode 100644 code/win32/vssver.scc 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_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_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/x_exe/mssccprj.scc create mode 100644 code/x_exe/vssver.scc create mode 100644 code/x_exe/x_exe.vcproj create mode 100644 code/x_game/mssccprj.scc create mode 100644 code/x_game/vssver.scc create mode 100644 code/x_game/x_game.vcproj 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/vssver.scc create mode 100644 code/zlib32/zip.h create mode 100644 code/zlib32/zipcommon.cpp create mode 100644 codemp/ALut.lib create mode 100644 codemp/CommandLine.txt create mode 100644 codemp/Debug/HA312W32.DLL create mode 100644 codemp/Debug/SHW32.DLL create mode 100644 codemp/Debug/vssver.scc create mode 100644 codemp/EaxMan.dll create mode 100644 codemp/JKA_mp.sln create mode 100644 codemp/JKA_mp.vssscc create mode 100644 codemp/OpenAL32.dll create mode 100644 codemp/OpenAL32.lib 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_InstanceFile.cpp create mode 100644 codemp/RMG/RM_InstanceFile.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_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/RMG/vssver.scc 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/Ratl/vssver.scc create mode 100644 codemp/Ravl/CVec.h create mode 100644 codemp/Ravl/vssver.scc 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/mssccprj.scc 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/Splines/vssver.scc create mode 100644 codemp/VU.bat create mode 100644 codemp/WinDed.dsp create mode 100644 codemp/WinDed.vcproj create mode 100644 codemp/WinDed.vcproj.vspscc 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.vcproj create mode 100644 codemp/botlib/botlib.vcproj.vspscc 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/botlib/mssccprj.scc create mode 100644 codemp/botlib/vssver.scc create mode 100644 codemp/buildvms.bat create mode 100644 codemp/cgame/JK2_cgame.def create mode 100644 codemp/cgame/JK2_cgame.vcproj create mode 100644 codemp/cgame/JK2_cgame.vcproj.vspscc create mode 100644 codemp/cgame/animtable.h create mode 100644 codemp/cgame/asm2mak.cfg 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.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/cgame.q3asm 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/mssccprj.scc create mode 100644 codemp/cgame/tr_types.h create mode 100644 codemp/cgame/vssver.scc create mode 100644 codemp/cleanvms.bat 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/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/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/OpenAL/vssver.scc 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_input.cpp 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/EaxMan.h create mode 100644 codemp/client/eax/eax.h create mode 100644 codemp/client/eax/vssver.scc create mode 100644 codemp/client/fffx.h create mode 100644 codemp/client/keycodes.h create mode 100644 codemp/client/keys.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/client/vssver.scc create mode 100644 codemp/encryption/encryption.h create mode 100644 codemp/encryption/vssver.scc create mode 100644 codemp/ff/ff_console.cpp create mode 100644 codemp/ff/vssver.scc 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.vcproj create mode 100644 codemp/game/JK2_game.vcproj.vspscc 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_GalakMech.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_Interrogator.c create mode 100644 codemp/game/NPC_AI_Jedi.c create mode 100644 codemp/game/NPC_AI_Mark1.c create mode 100644 codemp/game/NPC_AI_Mark2.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/asm2mak.cfg 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.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/game.q3asm create mode 100644 codemp/game/inv.h create mode 100644 codemp/game/match.h create mode 100644 codemp/game/mssccprj.scc 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/vssver.scc 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/ghoul2/vssver.scc 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/goblib/mssccprj.scc create mode 100644 codemp/goblib/vssver.scc create mode 100644 codemp/icarus/BlockStream.cpp create mode 100644 codemp/icarus/GameInterface.cpp create mode 100644 codemp/icarus/GameInterface.h create mode 100644 codemp/icarus/Instance.cpp create mode 100644 codemp/icarus/Interface.cpp create mode 100644 codemp/icarus/Interpreter.cpp create mode 100644 codemp/icarus/Memory.cpp 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/Sequencer.cpp create mode 100644 codemp/icarus/TaskManager.cpp create mode 100644 codemp/icarus/Tokenizer.cpp create mode 100644 codemp/icarus/blockstream.h create mode 100644 codemp/icarus/icarus.h create mode 100644 codemp/icarus/instance.h create mode 100644 codemp/icarus/interface.h create mode 100644 codemp/icarus/interpreter.h create mode 100644 codemp/icarus/module.h create mode 100644 codemp/icarus/sequence.h create mode 100644 codemp/icarus/sequencer.h create mode 100644 codemp/icarus/taskmanager.h create mode 100644 codemp/icarus/tokenizer.h create mode 100644 codemp/icarus/vssver.scc create mode 100644 codemp/install.bat create mode 100644 codemp/installvms.bat create mode 100644 codemp/jk2mp.vcproj create mode 100644 codemp/jk2mp.vcproj.vspscc 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/jpeg-6/vssver.scc 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/vssver.scc create mode 100644 codemp/mp3code/wavep.c create mode 100644 codemp/mssccprj.scc 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/vssver.scc create mode 100644 codemp/null/win_main.cpp create mode 100644 codemp/png/png.cpp create mode 100644 codemp/png/png.h create mode 100644 codemp/png/vssver.scc create mode 100644 codemp/qcommon/CNetProfile.cpp create mode 100644 codemp/qcommon/GenericParser2.cpp create mode 100644 codemp/qcommon/GenericParser2.h create mode 100644 codemp/qcommon/INetProfile.h create mode 100644 codemp/qcommon/MiniHeap.h create mode 100644 codemp/qcommon/RoffSystem.cpp create mode 100644 codemp/qcommon/RoffSystem.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/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.cpp 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/hstring.cpp create mode 100644 codemp/qcommon/hstring.h create mode 100644 codemp/qcommon/huffman.cpp create mode 100644 codemp/qcommon/md4.cpp 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/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/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/vssver.scc create mode 100644 codemp/qcommon/z_memman_console.cpp create mode 100644 codemp/qcommon/z_memman_pc.cpp 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/qgl.h create mode 100644 codemp/renderer/qgl_console.h create mode 100644 codemp/renderer/tr_WorldEffects.cpp create mode 100644 codemp/renderer/tr_WorldEffects.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_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/vssver.scc 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/NPCNav/vssver.scc 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_snapshot.cpp create mode 100644 codemp/server/sv_world.cpp create mode 100644 codemp/server/vssver.scc create mode 100644 codemp/smartheap/HA312W32.DLL create mode 100644 codemp/smartheap/HAW32M.LIB create mode 100644 codemp/smartheap/HEAPAGNT.H create mode 100644 codemp/smartheap/SHW32.DLL 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/smartheap/vssver.scc create mode 100644 codemp/strings/str_server.h create mode 100644 codemp/strings/vssver.scc create mode 100644 codemp/tonet.bat create mode 100644 codemp/tosend.bat create mode 100644 codemp/ui/asm2mak.cfg create mode 100644 codemp/ui/keycodes.h create mode 100644 codemp/ui/mssccprj.scc create mode 100644 codemp/ui/ui.bat create mode 100644 codemp/ui/ui.def create mode 100644 codemp/ui/ui.q3asm create mode 100644 codemp/ui/ui.vcproj create mode 100644 codemp/ui/ui.vcproj.vspscc 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.c create mode 100644 codemp/ui/ui_util.c create mode 100644 codemp/ui/vssver.scc create mode 100644 codemp/unix/files_linux.cpp 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/makefile 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.cpp create mode 100644 codemp/unix/vm_x86.c create mode 100644 codemp/unix/vssver.scc create mode 100644 codemp/update_MPents.bat create mode 100644 codemp/vssver.scc 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/snd_fx_img.h create mode 100644 codemp/win32/ui.rc create mode 100644 codemp/win32/vssver.scc 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_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_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_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/winquake.rc create mode 100644 codemp/x_botlib/mssccprj.scc create mode 100644 codemp/x_botlib/vssver.scc create mode 100644 codemp/x_botlib/x_botlib.vcproj create mode 100644 codemp/x_exe/mssccprj.scc create mode 100644 codemp/x_exe/vssver.scc create mode 100644 codemp/x_exe/x_exe.vcproj create mode 100644 codemp/x_jk2cgame/mssccprj.scc create mode 100644 codemp/x_jk2cgame/vssver.scc create mode 100644 codemp/x_jk2cgame/x_jk2cgame.vcproj create mode 100644 codemp/x_jk2game/mssccprj.scc create mode 100644 codemp/x_jk2game/vssver.scc create mode 100644 codemp/x_jk2game/x_jk2game.vcproj create mode 100644 codemp/x_ui/mssccprj.scc create mode 100644 codemp/x_ui/vssver.scc create mode 100644 codemp/x_ui/x_ui.vcproj 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/vssver.scc create mode 100644 codemp/zlib32/zip.h create mode 100644 codemp/zlib32/zipcommon.cpp create mode 100644 ui/menudef.h create mode 100644 ui/vssver.scc 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..adfd5a5 --- /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/0_compiled_first/vssver.scc b/code/0_compiled_first/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..bbe8e58d562c38de5c84fe4b863fe054a9ccf61d GIT binary patch literal 48 ycmXpJVq_>gDwNv&Y=_H|yDBkqiQiX5@3`El#KHgu%0PNueTSpX*~0IPKoJ0=mJg}` literal 0 HcmV?d00001 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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 mAn=IN_E~z8s-?19GF}U^Dd>k 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

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..c4eca5e --- /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..b731f47 --- /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..3eb821b --- /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..91495e7 --- /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..5251389 --- /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..28c263e --- /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..729722e --- /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..ae912a6 --- /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..b1c4be3 --- /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..382e8b6 --- /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..50b89c7 --- /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..48a5432 --- /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..20bdad9 --- /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..fd1a303 --- /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..a437d89 --- /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..bded10b --- /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..c423a06 --- /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..193fc52 --- /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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

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..e2a8c4f --- /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..d3e459c --- /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..e2f688e --- /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..8218812 --- /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..5b586f4 --- /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..302fe1f --- /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..1034b9d --- /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..2016219 --- /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..d7039ef --- /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..0484013 --- /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..aa79a1f --- /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..2548c47 --- /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..807eba8 --- /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..c1a22d3 --- /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..b0e3989 --- /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..bd870fd --- /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..46768dc --- /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..8fd98a4 --- /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..617ca75 --- /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..b5a72c3 --- /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..d88fc4e --- /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..74e8571 --- /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..bb34457 --- /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..7f1661d --- /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/FeelIt/vssver.scc b/code/win32/FeelIt/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..0e2ef87c9c823b047f7359e0e32b3a1d2d32f065 GIT binary patch literal 528 zcmW;IT_^(p5C?Fh2zjuC2PWCF7DmhzGY?8w!ic2U%4#99LTDi`Xh@NKgpAD#EyFO| z%*^L}3@MtAHXjd`&={q;-Mw7Df7e}I5|QB1xI?V!eNtsgE#z_%?_17c#S50aJ+PF} z1DFYc6gHnifMgl$J88Oabu^<_zzx=ZR%{*-y$YUBsnpb!kA zm)h(^^g6ia)+XkrvM8Rge*>JVon!O#MzZ^!8hkHPxMt$a=uNQwm+n%$-=H<_d1)W% z)AEegg5`&S!yUF4v<^J#q|$`7U+67xm1Mf~Z0rxM2d8TmIwQLW(c9n$Wr3-iIf6ET zmxywsPu3!O2Ru#v8}o2J+R$em{ZK18tT}KcK_!XobSk}cvM=@ qCtzLmKn&OLiarI)Ba(Y2PMzp8@CS{qxJ&Coo5Ah%`Stc)3Hl#CubM&t literal 0 HcmV?d00001 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..4a4b273 --- /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..021f031 --- /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..c6b3732 --- /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..8cd9d35 --- /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..cb08641 --- /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..02e352f --- /dev/null +++ b/code/win32/glw_win_dx8.h @@ -0,0 +1,180 @@ + +/* + * 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 + IDirect3DDevice8* 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 + { + IDirect3DTexture8* mipmap; + D3DTEXTUREFILTERTYPE minFilter, mipFilter, magFilter; + D3DTEXTUREADDRESS wrapU, wrapV; + float anisotropy; + }; + + 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 + D3DVIEWPORT8 viewport; + + // Clearing info + D3DCOLOR clearColor; + float clearDepth; + int clearStencil; + + // Widescreen mode + bool isWidescreen; + + // Global color + D3DCOLOR currentColor; + + // Scissoring + bool scissorEnable; + D3DRECT scissorBox; + + // Directional Light + D3DLIGHT8 dirLight; + D3DMATERIAL8 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(); +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..e669360 --- /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..fa16c02 --- /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/snd_fx_img.h b/code/win32/snd_fx_img.h new file mode 100644 index 0000000..d226885 --- /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/vssver.scc b/code/win32/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..04d99268c3db30a8600df0345484bb0aed885830 GIT binary patch literal 704 zcmW;KZAg<*6bJA--{vGMH%o0Y>TVB3p^mXA=!Jx3kuAYw_Tn7UhDf#4Tweq?w@*uC zlvxCYy{<{%TnQ8QVfi6RF)?k{%GoA3YMB)aENRyNxzC4t@9&<&fpgEJ77NIgmts>_ zd|2}+qNtNKtauErI_p3dVV@U!K&L4?sW-?4xEX}aWOeawzmeGoH+D@pd{PVZGdOR1 zP3j2kV4i_zhW6QV8z}Qzc(l;6*fKgo`FbH(*G{E>!=K>&m+$z)MQhAa`0TD%(=m^i z<;URp$}yKg9SvA6)<`SX{F6F+B8gFn~q^;iUlnA2f5y}3t} ztfo1rp8+pSM8`)iT%_4>CcJ9!ER6+aEI$j5S_@P@g@TF@7s5MIquQiWN%{B4h6i+y zCHie~ZXGOwr^Zg?pO^M={Cv#tQjJ4(I^M|YU4m_qb55!8D(k-)?khKH9oMgM{Jesj z2&o)2+dJ3iu>au&JfH|AoBVmqYj9#**4irz$d4hvcrzh?hKu#q`XV|1UQReS5#Rc~ IV2aK6KM)kY*#H0l literal 0 HcmV?d00001 diff --git a/code/win32/win_file.h b/code/win32/win_file.h new file mode 100644 index 0000000..0fafd40 --- /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..66dcc9d --- /dev/null +++ b/code/win32/win_file_xbox.cpp @@ -0,0 +1,173 @@ + +/* + * 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..7dd55f8 --- /dev/null +++ b/code/win32/win_filecode.cpp @@ -0,0 +1,346 @@ + +/* + * 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; + in = fopen("d:\\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 + s_Files->Insert(info, code); + } + + fclose(in); + Z_Free(baseAddr); + return true; +} + +bool Sys_SaveFileCodes(void) +{ + bool ret; + int res; + + // 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; + + // 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(); + + // 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_BuildOSPath(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..5849eea --- /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..309845c --- /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..ee7445e --- /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..42f8114 --- /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_input.cpp b/code/win32/win_input.cpp new file mode 100644 index 0000000..293cfaf --- /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..56d47cc --- /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..a5507f2 --- /dev/null +++ b/code/win32/win_input_console.cpp @@ -0,0 +1,523 @@ +// #include "../server/exe_headers.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" + + +static void HandleDebugJoystickPress(fakeAscii_t button); + +static bool _UIRunning = false; + +static bool IN_ControllerMustBePlugged(int controller); + +// Comment out next line to turn off debug controller. This should be +// forced off in FINAL_BUILD, but for now, I want to send builds to +// Activision made with FINAL_BUILD but that support the cheatpad. +#define DEBUG_CONTROLLER + +// Controller connection globals +static signed char uiControllerNotification = -1; +bool noControllersConnected = false; +bool wasPlugged[4]; + +PadInfo _padInfo; // gamepad thumbstick buffer + + + +//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) { + 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; + case A_JOY15: + return A_MOUSE1; +#ifdef _GAMECUBE + case A_JOY16: + return A_ESCAPE; + case A_JOY14: + return A_DELETE; +#else + case A_JOY14: + return A_ESCAPE; + case A_JOY16: + return A_DELETE; +#endif + + //left and right trigger for scrolling + case A_JOY11: + return A_PAGE_UP; + case A_JOY12: + return A_PAGE_DOWN; + + // start and back button on xbox + case A_JOY1: + //JLF + return A_ESCAPE; + case A_JOY2: + case A_JOY4: + //JLF + return A_MOUSE1; + //return button; + + case A_JOY3: + return A_MOUSE1; + } + + 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); +void IN_CommonJoyPress(int controller, fakeAscii_t button, bool pressed) +{ + // Check for special cases for map hack + // This should be #ifdef'd out in FINAL_BUILD, but I really don't care. + // If someone wants to copy the retail version to their modded xbox and + // edit the config file to turn on maphack, let them. + 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; + } + } + + + if(IN_GetMainController() == controller || _UIRunning) + { + // 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 +} + +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; + + // if the UI is running, then let all gamepad sticks work, else only main controller + if(_UIRunning) + Sys_QueEvent( 0, SE_MOUSE, _padInfo.joyInfo[1].x * 4.0f, _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; + } + } +} + +/********* +IN_DisplayControllerUnplugged +*********/ +static void IN_DisplayControllerUnplugged(int controller) +{ + uiControllerNotification = controller; + + //TODO Add a call to the UI that draws a controller disconnected message + // on the screen. +// VM_Call( uivm, UI_CONTROLLER_UNPLUGGED, true, controller); +} + +/********* +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); +} + +/********* +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(!_UIRunning && controller == IN_GetMainController()) + { + return true; + } + + if(noControllersConnected) + { + return true; + } + + return false; +} + +/********* +IN_PadUnplugged +*********/ +void IN_PadUnplugged(int controller) +{ + if(wasPlugged[controller]) + { + Com_Printf("\tController %d unplugged\n",controller); + } + + if(IN_ControllerMustBePlugged(controller)) + { + //If UI isn't busy, inform it about controller loss. + if(uiControllerNotification == -1) + { + IN_DisplayControllerUnplugged(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)) + { + //If UI is dealing with this controller, tell it to stop. + if(uiControllerNotification == controller) + { + IN_ClearControllerUnplugged(); + } + } + wasPlugged[controller] = true; + noControllersConnected = false; +} + +/********* +IN_GetMainController +*********/ +int IN_GetMainController(void) +{ + return cls.mainGamepad; +} + +/********* +IN_SetMainController +*********/ +void IN_SetMainController(int id) +{ + cls.mainGamepad = id; +} + +/********* +IN_SetThumbStickConfig +Sets the thumbstick configuration value +*********/ +void IN_SetThumbStickConfig(int configValue) +{ + Cvar_Set("ui_thumbStickMode", va("%i", configValue)); +} + +/********* +IN_SetButtonConfig +Execs a button configuration script based on configValue +*********/ +void IN_SetButtonConfig(int configValue) +{ + // Set the cvar + Cvar_Set("ui_buttonMode", va("%i", configValue)); + + // Exec the script + char execString[40]; + sprintf (execString, "exec cfg\\buttonConfig%i.cfg\n", configValue); + Cbuf_ExecuteText (EXEC_NOW, execString); +} + +/********* +IN_SetDpadConfig +Execs a dpad configuration script based on configValue +*********/ +void IN_SetDpadConfig(int configValue) +{ + // Set the cvar + Cvar_Set("ui_dpadMode", va("%i", configValue)); + + // Exec the script + char execString[40]; + sprintf (execString, "exec cfg\\dpadConfig%i.cfg\n", configValue); + Cbuf_ExecuteText (EXEC_NOW, execString); +} + +/********************************************************** +* +* DEBUGGING CODE +* +**********************************************************/ + +#ifdef DEBUG_CONTROLLER +static void HandleDebugJoystickPress(fakeAscii_t button) +{ + // Super hackalicious crap used below. Please remove this at some point. + static int curSaberSet = 0; + static int curPlayerSet = 0; + static short dpadmode = 0; + static short buttonmode = 0; + static short thumbmode = 0; + + switch(button) { + case A_JOY13: // Right pad up + Cbuf_ExecuteText(EXEC_APPEND, "give all\n"); + break; + case A_JOY16: // Right pad left + Cbuf_ExecuteText(EXEC_APPEND, "viewpos\n"); + break; + case A_JOY14: // Right pad right + Cbuf_ExecuteText(EXEC_APPEND, "noclip\n"); + break; + case A_JOY15: // Right pad down + Cbuf_ExecuteText(EXEC_APPEND, "god\n"); + break; + case A_JOY4: // Start + Cvar_SetValue("m_pitch", -Cvar_VariableValue("m_pitch")); + break; + case A_JOY1: // back + Cvar_SetValue("cl_autolevel", !Cvar_VariableIntegerValue("cl_autolevel")); + break; + case A_JOY2: // Left thumbstick + extern void Z_CompactStats(void); + Z_CompactStats(); + break; + case A_JOY12: // Upper right trigger + Cbuf_ExecuteText(EXEC_APPEND, "load dbg-game\n"); + break; + case A_JOY8: // Left pad left + thumbmode++; + if(thumbmode == 4) + { + thumbmode = 0; + } + IN_SetThumbStickConfig(thumbmode); + break; + case A_JOY6: // Left pad right + dpadmode++; + if(dpadmode == 4) + { + dpadmode = 0; + } + IN_SetDpadConfig(0); + break; + case A_JOY5: // Left pad up + buttonmode++; + if(buttonmode == 4) + { + buttonmode = 0; + } + IN_SetButtonConfig(buttonmode); + break; + case A_JOY7: // Left pad down +// Cbuf_ExecuteText(EXEC_APPEND, "vid_restart\n"); + extern void Sys_Reboot(const char *reason); + Sys_Reboot("multiplayer"); + break; + case A_JOY11: // Upper left trigger + Cbuf_ExecuteText(EXEC_APPEND, "save dbg-game\n"); + break; + case A_JOY9: // White button + // Hacky. Really hacky. No, hackier than that. + curSaberSet = (curSaberSet + 1) % 3; // Number of xsaber strings in config file + Cbuf_ExecuteText(EXEC_APPEND, va("vstr xsaber%d\n", curSaberSet)); + break; + case A_JOY10: // Black button + curPlayerSet = (curPlayerSet + 1) % 6; // Number of xplayer strings in config file + Cbuf_ExecuteText(EXEC_APPEND, va("vstr xplayer%d\n", curPlayerSet)); + break; + } +} + +#endif + diff --git a/code/win32/win_input_rumble.cpp b/code/win32/win_input_rumble.cpp new file mode 100644 index 0000000..3cd6e30 --- /dev/null +++ b/code/win32/win_input_rumble.cpp @@ -0,0 +1,707 @@ + +/* + * 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); + +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; + +/***** FIXME Some functions that would be found in a client manager *****/ +/***** BEGIN FILLER *****/ + +// Always return 0 because we have only one client (right now anyway) +int ActiveClientNum(void) +{ + return 0; +} + +// The active controller will always be number 0 for now +int ActiveController(void) +{ + return 0; +} +/***** END FILLER *****/ + +void IN_enableRumble( void ) +{ + if (ActiveClientNum() == 0) + { + Cvar_Set( "in_useRumble", "1"); + } + else + { + Cvar_Set( "in_useRumble2", "1"); + } +} + +void IN_disableRumble( void ) +{ + if (ActiveClientNum() == 0) + { + Cvar_Set( "in_useRumble", "0"); + } + else + { + Cvar_Set( "in_useRumble2", "0"); + } +} + +bool IN_usingRumble( void ) +{ + if (ActiveClientNum() == 0) + { + //return Cvar_VariableIntegerValue( "in_useRumble"); + return in_useRumble->integer; + } + else + { + //return Cvar_VariableIntegerValue( "in_useRumble2"); + return in_useRumble->integer; + } + + return true; +} + + +// 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; +} + +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 = IN_Time() + cmd; + + rumbleStatus[rumbleScripts[whichScript].controller].changed = true; + rumbleStatus[rumbleScripts[whichScript].controller].killed = false; +} + + + +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); +} + +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; +} + +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 +void IN_UpdateRumbleFromStates() +{ + //if (!IN_usingRumble()) return; +/*mb extern int G_ShouldBeRumbling(); + if (!G_ShouldBeRumbling()) + return; +*/ + int usingRumble[2]; + usingRumble[0] = Cvar_VariableIntegerValue("in_useRumble"); + usingRumble[1] = Cvar_VariableIntegerValue("in_useRumble2"); + + int i; + int value[MAX_RUMBLE_CONTROLLERS][2]; + int cur_time = IN_Time(); + + 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 ( !usingRumble[ActiveClientNum()] ) + { + IN_KillRumbleScript(i); + continue; + } +/*mb + if (!ClientManager::ActiveGentity() || !G_ActivePlayerNormal()) + { + IN_KillRumbleScript(i); + continue; + } +*/ + // Unset state so skip + if ( rumbleScripts[i].nextStateAt == 0) continue; + + // 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; + } +} + + + +/* +================== +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 ); + Cvar_Get("in_useRumble2", "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..ab7d59d --- /dev/null +++ b/code/win32/win_input_xbox.cpp @@ -0,0 +1,306 @@ +// 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(); + +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 + +========================================================================= +*/ +// 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) && 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_LEFT + 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(); +} + +/* +================== +IN_Frame + +Called every frame, even if not generating commands +================== +*/ +//extern int ignoreInputTime; +void IN_Frame (void) +{ + 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); + } + else + { + IN_CheckForNoControllers(); + } + + // 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_local.h b/code/win32/win_local.h new file mode 100644 index 0000000..86b1e09 --- /dev/null +++ b/code/win32/win_local.h @@ -0,0 +1,76 @@ +// 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 ) + diff --git a/code/win32/win_main.cpp b/code/win32/win_main.cpp new file mode 100644 index 0000000..386dc46 --- /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 inline bool Sys_IsExpired() +{ +#if 0 +// sec min Hr Day Mon Yr + struct tm t_valid_start = { 0, 0, 8, 5, 8, 103 }; //zero based months! +// sec min Hr Day Mon Yr + struct tm t_valid_end = { 0, 0, 20, 13, 8, 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..3753dba --- /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..21a6d29 --- /dev/null +++ b/code/win32/win_main_console.cpp @@ -0,0 +1,584 @@ +#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" + +#ifdef _XBOX +#include +#define NEWDECL __cdecl + +#ifndef FINAL_BUILD +#include "dbg_console_xbox.h" +#endif + +#endif + +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); +} + + + + +char *Sys_Cwd( void ) { + static char cwd[MAX_OSPATH]; + +#ifdef _XBOX + strcpy(cwd, "d:"); +#endif + +#ifdef _GAMECUBE + strcpy(cwd, "."); +#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 + +===================== +*/ + +// 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. +#define LAUNCH_MAGIC "J3D1" +bool Sys_QuickStart( void ) +{ + static bool retVal = false; + static bool initialized = false; + + if( initialized ) + return retVal; + + initialized = true; + + DWORD launchType; + LAUNCH_DATA ld; + + if( (XGetLaunchInfo( &launchType, &ld ) != ERROR_SUCCESS) || + (launchType != LDT_TITLE) || + strcmp((const char *)&ld.Data[0], LAUNCH_MAGIC) ) + return (retVal = false); + + return (retVal = true); +} + +void Sys_Reboot( const char *reason ) +{ + LAUNCH_DATA ld; + const char *path = NULL; + + memset( &ld, 0, sizeof(ld) ); + + if (!Q_stricmp(reason, "multiplayer")) + { + path = "d:\\jamp.xbe"; + } + 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(); + + XLaunchNewImage(path, &ld); + + // This function should not return! + Com_Error( ERR_FATAL, "ERROR: XLaunchNewImage returned\n" ); +} + + +/* +================== +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(); + + Win_Init(); + Com_Init( "" ); + + // 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 + if (strstr(directory, "d:\\base\\")) + directory += 8; + + 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..cb82a92 --- /dev/null +++ b/code/win32/win_qal_xbox.cpp @@ -0,0 +1,1301 @@ + +/* + * 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 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 * 10.f, DS3D_DEFERRED); + v->second->SetMinDistance(value, DS3D_DEFERRED); +// v->second->SetMinDistance(value / 2.f, DS3D_DEFERRED); + } +} + +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; + } + + const char* name = Sys_GetFileCodeName(file); + + 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 + WaitForSingleObject(s_pState->m_Stream.m_Mutex, INFINITE); + s_pState->m_Stream.m_Queue.push_back(req); + ReleaseMutex(s_pState->m_Stream.m_Mutex); + + // 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.f) + { + // mute the sound + voice->SetVolume(DSBVOLUME_MIN); + } + else + { + // convert to dB + g = 20.f * log10(g); + + if(g < -100.0f) { + g = -100.0f; + } + + // set the volume + voice->SetVolume(g * 100.f); + } +} + +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.f) + { + // mute the sound + s_pState->m_Stream.m_pVoice->SetVolume(DSBVOLUME_MIN); + } + else + { + // convert to dB + g = 20.f * log10(g); + + if(g < -100.0f) { + g = -100.0f; + } + + // set the volume + s_pState->m_Stream.m_pVoice->SetVolume(g * 100.f); + } + + 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..f488748 --- /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..b59911d --- /dev/null +++ b/code/win32/win_qgl_dx8.cpp @@ -0,0 +1,6680 @@ + +/* + * 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" + +#ifdef _XBOX +#include +//#include "win_flareeffect.h" +#include "win_lighteffects.h" +#include "win_highdynamicrange.h" + +#ifndef FINAL_BUILD +#include +#endif + +#endif + +#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(IDirect3DTexture8* 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 + + +#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; + else 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); \ +} + +/* +================= +_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) +{ + 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->Release(); + 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, cnt; + 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) + { + 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 + _updateDrawStride(normals, tex0, tex1); + _updateShader(normals, tex0, tex1); + _updateTextures(); + _updateMatrices(); + + glw_state->drawStride += normals ? 2 : 1; + + 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; + + // 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; + } + + // 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 + + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_XYZ, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 4;//3; + + if(normals) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_NORMAL, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 4;//3; + } + + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_COLOR, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes;//1; + + if(tex0) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_TEX0, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 2;//2; + } + + if(tex1) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_TEX1, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + } + + // 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 +} + +// EXTENSION: End drawing for a frame +static void dllEndFrame(void) +{ + assert(!glw_state->inDrawBlock); + + // the blend state can get reset by Present()... + GLboolean blend = qglIsEnabled(GL_BLEND); + + glw_state->device->EndScene(); + + qglViewport(0, 0, glConfig.vidWidth, glConfig.vidHeight); + 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; + 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 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 = 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.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)stream & 0x7fffffff; + stream += tess.numVertexes * 4; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1724, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + 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 * 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_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; + stream += tess.numVertexes * 4; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1724, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + 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; + stream += tess.numVertexes * 4; + + *glw_state->drawArray++ = D3DPUSH_ENCODE(0x1724, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + 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) +{ + return; + + /* + // assert((format == GL_LIN_RGB8) && (type == GL_UNSIGNED_BYTE)); + + // create a temporary storage surface + IDirect3DSurface8 *target; + glw_state->device->CreateImageSurface(twidth, theight, D3DFMT_A8R8G8B8, &target); + + // get a pointer to the back buffer + IDirect3DSurface8* screen; + glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &screen); + + // copy the back buffer into a surface of the appropriate size and format + RECT r; + r.left = x; + r.top = y; + r.right = x + width; + r.bottom = y + height; + D3DXLoadSurfaceFromSurface(target, NULL, NULL, screen, NULL, &r, D3DX_DEFAULT, 0); + screen->Release(); + + // lock the target surface + D3DLOCKED_RECT lock; + target->LockRect(&lock, NULL, D3DLOCK_READONLY); + + // copy the pixel data + for (int y = 0; y < theight; ++y) + { + memcpy((char*)pixels + twidth * y * 4, + (char*)lock.pBits + lock.Pitch * y, + twidth * 4); + } + + // all done + target->UnlockRect(); + target->Release(); + */ + + /* + // create target storage surface + IDirect3DSurface8 *target; + glw_state->device->CreateImageSurface(twidth, theight, D3DFMT_A8R8G8B8, &target); + + // get a pointer to the back buffer + IDirect3DSurface8* back; + glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &back); + + D3DSURFACE_DESC ddsd; + back->GetDesc(&ddsd); + int bpp = XGBytesPerPixelFromFormat(ddsd.Format); + + // create surface to hold screen data + // (512x512 is exactly big enough to hold the screen but we + // need to save some memory so I'm going to clip the edges) + IDirect3DSurface8 *screen; + glw_state->device->CreateImageSurface(512, 512, ddsd.Format, &screen); + + // copy back buffer to non-tiled screen surface + RECT r; + r.left = 64; + r.top = 0; + r.right = 576; + r.bottom = 480; + POINT ul = {0, 0}; + glw_state->device->CopyRects(back, &r, 1, screen, &ul); + back->Release(); + + // deswizzle the screen + D3DLOCKED_RECT lock; + screen->LockRect(&lock, NULL, D3DLOCK_READONLY); + void* deswizzled = Z_Malloc(512*512*bpp, TAG_TEMP_WORKSPACE, qfalse, 16); + XGUnswizzleRect(lock.pBits, 512, 512, NULL, deswizzled, 0, NULL, bpp); + screen->UnlockRect(); + screen->Release(); + + // copy the screen into a surface of the appropriate size and format + r.left = 0; + r.top = 0; + r.right = 512; + r.bottom = 480; + D3DXLoadSurfaceFromMemory(target, NULL, NULL, deswizzled, ddsd.Format, + 512*bpp, NULL, &r, D3DX_DEFAULT, 0); + Z_Free(deswizzled); + + // lock the target surface + target->LockRect(&lock, NULL, D3DLOCK_READONLY); + + // copy the pixel data + for (int y = 0; y < theight; ++y) + { + memcpy((char*)pixels + twidth * y * 4, + (char*)lock.pBits + lock.Pitch * y, + twidth * 4); + } + + // all done + target->UnlockRect(); + target->Release(); + */ +} +/********** +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 +**********/ +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; + D3DBaseTexture* pTexStage0; + D3DBaseTexture* pTexStage1; + D3DBaseTexture* pTexStage2; + D3DBaseTexture* pTexStage3; + 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 ); + glw_state->device->GetTexture(0,&pTexStage0); + glw_state->device->GetTexture(1,&pTexStage1); + glw_state->device->GetTexture(2,&pTexStage2); + glw_state->device->GetTexture(3,&pTexStage3); + 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) + { + int refCount; + refCount = info->mipmap->Release(); + + // Had to remove this. Multiple characters using force push in one + // frame triggers the assert. Things clean up by the end of the frame. +// assert(refCount == 0); + + glw_state->device->CreateTexture( width, + height, + 1, + 0, + desc.Format, + 0, + &info->mipmap ); + } + + // 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 + glw_state->device->CreateTexture( w, + h, + 1, + 0, + D3DFMT_LIN_X8R8G8B8, + 0, + &pRenderTex ); + } + 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); + + // clear the other texture stages + glw_state->device->SetTexture(1, NULL); + glw_state->device->SetTexture(2, NULL); + glw_state->device->SetTexture(3, NULL); + + // 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); + + glw_state->device->SetTexture(0,pTexStage0); + glw_state->device->SetTexture(1,pTexStage1); + glw_state->device->SetTexture(2,pTexStage2); + glw_state->device->SetTexture(3,pTexStage3); + 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 ); + + // release our surfaces/textures + if(pTexStage0) + pTexStage0->Release(); + + if(pTexStage1) + pTexStage1->Release(); + + if(pTexStage2) + pTexStage2->Release(); + + if(pTexStage3) + pTexStage3->Release(); + + 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 _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)); + } +} + +static void _texImageDDS(glwstate_t::TextureInfo* info, GLint numlevels, GLsizei width, GLsizei height, GLenum format, const GLvoid *pixels) +{ + _d3d_check(D3DXCreateTextureFromFileInMemoryEx(glw_state->device, + pixels, + Z_Size(const_cast(pixels)), + width, + height, + numlevels, + 0, + D3DFMT_UNKNOWN, + D3DPOOL_MANAGED, + D3DX_DEFAULT, + D3DX_DEFAULT, + 0, + NULL, + NULL, + &info->mipmap), + "D3DXCreateTextureFromFileInMemoryEx"); +} + +static void _texImageRGBA(glwstate_t::TextureInfo* info, GLint numlevels, GLint internalformat, GLsizei width, GLsizei height, GLenum format, const GLvoid *pixels) +{ + IDirect3DSurface8 *pSurf = NULL; + D3DFORMAT f; + float bpp; + int pitch; + RECT srcRect; + + srcRect.top = 0; + srcRect.left = 0; + srcRect.right = width; + srcRect.bottom = height; + + switch(format) + { + case GL_RGB: + f = D3DFMT_X8R8G8B8; + bpp = 3; + break; + + case GL_RGBA: + f = D3DFMT_A8R8G8B8; + bpp = 4; + break; + + case GL_LIN_RGBA: + f = D3DFMT_LIN_A8R8G8B8; + bpp = 4; + break; + + case GL_LIN_RGB: + f = D3DFMT_LIN_X8R8G8B8; + bpp = 4; + break; + + case GL_LIN_RGB8: + f = D3DFMT_LIN_X8R8G8B8; + bpp = 4; + break; + + case GL_RGB8: + f = D3DFMT_X8R8G8B8; + bpp = 4; + break; + + case GL_RGB_SWIZZLE_EXT: + f = D3DFMT_R5G6B5; + bpp = 2; + break; + + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + f = D3DFMT_DXT1; + bpp = 0.5; + break; + + default: + assert(0); + } + + _d3d_check(glw_state->device->CreateImageSurface(width, height, f, &pSurf), + "CreateImageSurface"); + + pitch = (int)((float)width * bpp); + + _d3d_check(D3DXLoadSurfaceFromMemory(pSurf, + NULL, + NULL, + (LPCVOID)pixels, + f, + pitch, + NULL, + &srcRect, + D3DX_FILTER_NONE, + 0), + "D3DXLoadSurfaceFromMemory"); + + switch(internalformat) + { + case GL_RGB5: + case GL_RGB4_S3TC: + f = D3DFMT_R5G6B5; + break; + + case GL_RGBA4: + f = D3DFMT_A4R4G4B4; + break; + + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + f = D3DFMT_DXT1; + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + f = D3DFMT_DXT5; + break; + + case GL_RGB8: + case 3: + f = D3DFMT_X8R8G8B8; + break; + + case GL_LIN_RGBA8: + f = D3DFMT_LIN_A8R8G8B8; + break; + case GL_LIN_RGB8: + f = D3DFMT_LIN_X8R8G8B8; + break; + + case GL_RGBA8: + case 4: + f = D3DFMT_A8R8G8B8; + break; + case GL_RGB: + f = D3DFMT_X8R8G8B8; + break; + + default: + assert(0); + } + + _d3d_check(D3DXCreateTexture(glw_state->device, + width, + height, + numlevels, + 0, + f, + D3DPOOL_MANAGED, + &info->mipmap), + "D3DXCreateTexture"); + + LPDIRECT3DSURFACE8 txtSurf; + _d3d_check(info->mipmap->GetSurfaceLevel(0, &txtSurf), + "GetSurfaceLevel"); + + _d3d_check(D3DXLoadSurfaceFromSurface(txtSurf, NULL, NULL, + pSurf, NULL, NULL, D3DX_DEFAULT, 0), + "D3DXLoadSurfaceFromSurface"); + + txtSurf->Release(); + pSurf->Release(); + + if(numlevels > 1) + { + _d3d_check(D3DXFilterTexture(info->mipmap, + NULL, + D3DX_DEFAULT, + D3DX_DEFAULT), + "D3DXFilterTexture"); + } +} + +// 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; + info->mipmap->Release(); + } + // 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 +}; + + +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; + + if( XGetVideoFlags() & XC_VIDEO_FLAGS_HDTV_480p ) + { + width = 720; + 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); + + 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; +#endif // VV_LIGHTING + HDREffect.Initialize(); +#endif + +#ifndef FINAL_BUILD + Cmd_AddCommand("d3d_autoperf", D3D_AutoPerfData_f); +#endif +} + +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 CSS_IMAGE_HDR_SIZE 2048 +#define CSS_IMAGE_WH 256 +#define CSS_IMAGE_DATA_SIZE ((CSS_IMAGE_WH * CSS_IMAGE_WH) / 2 ) + +struct XprImageHeader +{ + XPR_HEADER xpr; // Standard XPR struct + IDirect3DTexture8 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 a copy of the backbuffer to a .xbx file specified by filename +//----------------------------------------------------------------------------- +void SaveCompressedScreenshot(const char* filename) +{ + 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; + glw_state->device->CreateImageSurface( CSS_IMAGE_WH, CSS_IMAGE_WH, D3DFMT_DXT1, &compressedSaveGameImage ); + D3DXLoadSurfaceFromSurface( compressedSaveGameImage, NULL, NULL, screenShot, NULL, NULL, D3DX_DEFAULT, D3DCOLOR( 0 ) ); + + // Free the big screenshot 640x480x4? + if ( screenShot ) + screenShot->Release(); + + // Write out the saveimage to the utility drive + res = XGWriteSurfaceOrTextureToXPR( compressedSaveGameImage, filename, TRUE ); + + // Free the compressed CSS_IMAGE_WH x CSS_IMAGE_WH image + if ( compressedSaveGameImage ) + compressedSaveGameImage->Release(); +} + +//----------------------------------------------------------------------------- +// LoadCompressedScreenshot +// Loads a .xbx screenshot file and replaces the current texture +//----------------------------------------------------------------------------- +BOOL LoadCompressedScreenshot(const char* filename) +{ + // get the current texture + glwstate_t::TextureInfo* info = _getCurrentTexture(glw_state->serverTU); + if (info == NULL) return FALSE; + + // locals + LPDIRECT3DTEXTURE8 lpThumbTex; + XprImageHeader XprHeader; + 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 ) + { + DWORD err = GetLastError(); + + // If there was a problem, we might want to load the default image for TRC + // TCR C4-18 Saved Game Representative Image + // No specific image found; see if the default save image exists + /* + hFile = CreateFile( "u:\\default_screen.xbx", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL ); + if( hFile == INVALID_HANDLE_VALUE ) + { + m_bIsValidImage = FALSE; + return FALSE; + } + */ + return FALSE; + } + + + // Read the image header from disk + bSuccess = ReadFile( hFile, &XprHeader, sizeof( XprImageHeader ), &dwBytesRead, NULL ); + + // Validate the image + bSuccess &= dwBytesRead == sizeof( XprImageHeader ) && + XprHeader.xpr.dwMagic == XPR_MAGIC_VALUE && + XprHeader.xpr.dwTotalSize == CSS_IMAGE_HDR_SIZE + CSS_IMAGE_DATA_SIZE && + XprHeader.xpr.dwHeaderSize == CSS_IMAGE_HDR_SIZE && + XprHeader.dwEndOfHeader == 0xFFFFFFFF; + + // If image looks good, store the bits in a texture + if( bSuccess ) + { + HRESULT hr; + hr = glw_state->device->CreateTexture( CSS_IMAGE_WH, + CSS_IMAGE_WH, + 1, + 0, + D3DFMT_DXT1, + D3DPOOL(), + &lpThumbTex ); + bSuccess = SUCCEEDED(hr); + + if( bSuccess ) + { + D3DLOCKED_RECT lr; + lpThumbTex->LockRect( 0, &lr, NULL, D3DLOCK_READONLY ); + + // Copy the bits from the file to the texture + SetFilePointer( hFile, CSS_IMAGE_HDR_SIZE, NULL, FILE_BEGIN ); + bSuccess = ReadFile( hFile, lr.pBits, CSS_IMAGE_DATA_SIZE, &dwBytesRead, NULL ); + bSuccess &= ( dwBytesRead == CSS_IMAGE_DATA_SIZE ); + lpThumbTex->UnlockRect( 0 ); + + // If everything was ok, then set the texture ptrs + DWORD refcount; + if( bSuccess ) + { + refcount = info->mipmap->Release(); + + assert(refcount == 0); + + info->mipmap = lpThumbTex; + info->mipmap->AddRef(); + + refcount = lpThumbTex->Release(); + + assert(refcount == 1); + } + else + { + refcount = lpThumbTex->Release(); + + assert(refcount == 0); + } + + } + } + else + { + return FALSE; + } + + CloseHandle( hFile ); + return bSuccess; +} + +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..440ac5b --- /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..074dd3f --- /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..9fc0dcb --- /dev/null +++ b/code/win32/win_stencilshadow.cpp @@ -0,0 +1,437 @@ +// +// +// 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" + + +StencilShadow StencilShadower; + + +StencilShadow::StencilShadow() +{ +} + + +StencilShadow::~StencilShadow() +{ +} + + +void StencilShadow::AddEdge( int i1, int i2, int facing ) +{ + 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 ]++; +} + + +void StencilShadow::RenderEdges() +{ + // int i; + //int c, c2; + //int j, k; + //int i2; + //int c_edges, c_rejected; + //int hit[2]; + + //// 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 = m_numEdgeDefs[ i ]; + // for ( j = 0 ; j < c ; j++ ) + // { + // if ( !m_edgeDefs[ i ][ j ].facing ) + // { + // continue; + // } + + // 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 ) + // { + // VectorCopy( tess.xyz[i], m_shadowVerts[0] ); + // VectorCopy( tess.xyz[i + tess.numVertexes], m_shadowVerts[1] ); + // VectorCopy( tess.xyz[i2], m_shadowVerts[2] ); + // VectorCopy( tess.xyz[i2 + tess.numVertexes], m_shadowVerts[3] ); + + // c_edges++; + + // glw_state->device->SetVertexShader( D3DFVF_XYZ ); + // glw_state->device->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, m_shadowVerts, sizeof(vec3_t) ); + // } + // else + // { + // c_rejected++; + // } + // } + //} + + int i; + int c; + int j; + int i2; + int c_edges, c_rejected; + int numTris; + int o1, o2, o3; + + // 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; + + int nVerts = 0, numPrims = 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; + } + + //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 + i2 = m_edgeDefs[ i ][ j ].i2; + VectorCopy( tess.xyz[i], m_shadowVerts[nVerts++] ); + VectorCopy( tess.xyz[i + tess.numVertexes], m_shadowVerts[nVerts++] ); + VectorCopy( tess.xyz[i2 + tess.numVertexes], m_shadowVerts[nVerts++] ); + VectorCopy( tess.xyz[i2], m_shadowVerts[nVerts++] ); + numPrims++; + } + } + + if(!numPrims || !nVerts) + return; + + glw_state->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_DISABLE ); + + glw_state->device->SetVertexShader( D3DFVF_XYZ ); + glw_state->device->DrawPrimitiveUP( D3DPT_QUADLIST, numPrims, m_shadowVerts, sizeof(vec3_t) ); + + nVerts = 0; + numPrims = 0; + + //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 ]; + + VectorCopy( tess.xyz[o1], m_shadowVerts[nVerts++] ); + VectorCopy( tess.xyz[o2], m_shadowVerts[nVerts++] ); + VectorCopy( tess.xyz[o3], m_shadowVerts[nVerts++] ); + VectorCopy( tess.xyz[o3 + tess.numVertexes], m_shadowVerts[nVerts++] ); + VectorCopy( tess.xyz[o2 + tess.numVertexes], m_shadowVerts[nVerts++] ); + VectorCopy( tess.xyz[o1 + tess.numVertexes], m_shadowVerts[nVerts++] ); + numPrims += 2; + } + + glw_state->device->SetVertexShader( D3DFVF_XYZ ); + glw_state->device->DrawPrimitiveUP( D3DPT_TRIANGLELIST, numPrims, m_shadowVerts, sizeof(vec3_t) ); +} + + +bool StencilShadow::BuildFromLight( VVdlight_t *dl ) +{ + // int i; + //int numTris; + //vec3_t lightDir; + //D3DXMATRIX matWorldInv; + //D3DXVECTOR4 viewLightPos; + + //// we can only do this if we have enough space in the vertex buffers + //if ( tess.numVertexes >= SHADER_MAX_VERTEXES / 2 ) { + // return false; + //} + + //// project vertexes away from light direction + //for ( i = 0 ; i < tess.numVertexes ; i++ ) + //{ + // // Get the light direction to the vertex + // VectorCopy( backEnd.currentEntity->lightDir, lightDir ); + + // VectorMA( tess.xyz[i], -512, lightDir, tess.xyz[i+tess.numVertexes] ); + //} + + 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 + vec3_t worldxyz, ld; + float groundDist, extlength; + + VectorCopy( backEnd.currentEntity->lightDir, 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.5 ) { + VectorMA( lightDir, (0.5 - 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); + + //Oh well, just cast them straight down no matter what onto the ground plane. + //This presents no chance of screwups and still looks better than a stupid + //shader blob. + //VectorSet(lightDir, 0.0f, 0.0f, 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 += 2.0f; //fudge factor + //VectorMA( tess.xyz[i], -groundDist, lightDir, tess.xyz[i+tess.numVertexes] ); + VectorMA( tess.xyz[i], -200.0f, lightDir, tess.xyz[i+tess.numVertexes] ); + } + + + // decide which triangles face the light + memset( m_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 ]; + + 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; +} + + +void StencilShadow::RenderShadow() +{ + DWORD lighting, fog, srcblend, destblend, alphablend, zwrite, zfunc; + + 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 ); + + GL_Bind( tr.whiteImage ); + + glw_state->device->SetRenderState( D3DRS_LIGHTING, FALSE ); + glw_state->device->SetRenderState( D3DRS_FOGENABLE, FALSE ); + + // 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 ); + glw_state->device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_INCR ); + glw_state->device->SetRenderState( D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP ); + + // If ztest passes, inc/decrement stencil buffer value + glw_state->device->SetRenderState( D3DRS_STENCILREF, 0x1 ); + glw_state->device->SetRenderState( D3DRS_STENCILMASK, 0xffffffff ); + glw_state->device->SetRenderState( D3DRS_STENCILWRITEMASK, 0xffffffff ); + glw_state->device->SetRenderState( D3DRS_STENCILPASS, D3DSTENCILOP_KEEP ); + + // Make sure that no pixels get drawn to the frame buffer + glw_state->device->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE ); + glw_state->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ZERO ); + glw_state->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE ); + + glw_state->device->SetTransform(D3DTS_VIEW, + glw_state->matrixStack[glwstate_t::MatrixMode_Model]->GetTop()); + + glw_state->device->SetTexture(0, NULL); + glw_state->device->SetTexture(1, NULL); + + qglCullFace( GL_FRONT ); + + // Draw front-side of shadow volume in stencil/z only + RenderEdges(); + + // Now reverse cull order so back sides of shadow volume are written. + qglCullFace( GL_BACK ); + + // Decrement stencil buffer value + glw_state->device->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_DECR ); + + // Draw back-side of shadow volume in stencil/z only + RenderEdges(); + + // Restore render states + 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, D3DCULL_CCW ); +} + + +void StencilShadow::FinishShadows() +{ + 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_STENCILWRITEMASK, 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, 0x7f000000 ); + + 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 ); +} diff --git a/code/win32/win_stencilshadow.h b/code/win32/win_stencilshadow.h new file mode 100644 index 0000000..35ba45d --- /dev/null +++ b/code/win32/win_stencilshadow.h @@ -0,0 +1,45 @@ +// +// +// win_stencilshadow.h +// +// Declaration for stencil shadowing class +// +// + +#ifndef _WIN_STENCILSHADOW_H_ +#define _WIN_STENCILSHADOW_H_ + +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). + short i2; + short facing; +} edgeDef_t; + +#define MAX_EDGE_DEFS 16 + + +class StencilShadow +{ +public: + + edgeDef_t m_edgeDefs[SHADER_MAX_VERTEXES][MAX_EDGE_DEFS]; + int m_numEdgeDefs[SHADER_MAX_VERTEXES]; + int m_facing[SHADER_MAX_INDEXES/3]; + vec3_t m_shadowVerts[SHADER_MAX_VERTEXES * 8]; + + StencilShadow(); + ~StencilShadow(); + void AddEdge( int i1, int i2, int facing ); + void RenderEdges(); + bool BuildFromLight( VVdlight_t *dl ); + 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..1ea0875 --- /dev/null +++ b/code/win32/win_stream_dx8.cpp @@ -0,0 +1,290 @@ + +/* + * 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" + +#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 const char* Sys_GetFileCodeName(int code); +extern int Sys_GetFileCodeSize(int code); + +#define STREAM_MAX_OPEN 48 +struct StreamInfo +{ + volatile HANDLE 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; + + +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); + + // Process request + switch (req.type) + { + case IOREQ_OPEN: + strm = &s_Streams[req.handle]; + assert(strm->used); + + { + 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); + +# if STREAM_SLOW_READ + Sleep(200); +# endif + + 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) +{ + // 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 + int size = Sys_GetFileCodeSize(code); + 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; + } +} diff --git a/code/win32/win_syscon.cpp b/code/win32/win_syscon.cpp new file mode 100644 index 0000000..439d8c7 --- /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..d836916 --- /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..e884a40 --- /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..140f0a5 --- /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/x_exe/mssccprj.scc b/code/x_exe/mssccprj.scc new file mode 100644 index 0000000..3bfb92d --- /dev/null +++ b/code/x_exe/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[x_exe.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/code/x_exe", YUHAAAAA diff --git a/code/x_exe/vssver.scc b/code/x_exe/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..4e7812608cc2bc4dca8a4da8a9b9e02a71d86835 GIT binary patch literal 48 ycmXpJVq_>gDwNv&Y=_H|yDBkqiQiYe32bYbBf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/x_game/mssccprj.scc b/code/x_game/mssccprj.scc new file mode 100644 index 0000000..c5430d3 --- /dev/null +++ b/code/x_game/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[x_game.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/code/x_game", XUHAAAAA diff --git a/code/x_game/vssver.scc b/code/x_game/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..fd18bfa921766902987b610bf46968d1275fda11 GIT binary patch literal 48 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiWo+_P-{Y!L=9SO=toTkJPVAIeZ?2Z{gyx~vcL literal 0 HcmV?d00001 diff --git a/code/x_game/x_game.vcproj b/code/x_game/x_game.vcproj new file mode 100644 index 0000000..aab4eaf --- /dev/null +++ b/code/x_game/x_game.vcproj @@ -0,0 +1,1019 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/zlib32/deflate.cpp b/code/zlib32/deflate.cpp new file mode 100644 index 0000000..0301b6a --- /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..1f7d4a0 --- /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..887bb23 --- /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..550244d --- /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/vssver.scc b/code/zlib32/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..4cf098d90e93ab9ecacf9c2c0f3e66d0f60f3424 GIT binary patch literal 128 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZ3v(_uH6=VPdJ0M-yzRY3w6Crh0pol$?Z?QV$ zU2dg{IwO$p0OWfro3k-$n}OvWf&8+QEVrG_52=CVoq&9{zFy~E=5t`aGmxKBJ@>_A It?OVu0F-JUCIA2c literal 0 HcmV?d00001 diff --git a/code/zlib32/zip.h b/code/zlib32/zip.h new file mode 100644 index 0000000..1a9fd2d --- /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..77f56cd --- /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/ALut.lib b/codemp/ALut.lib new file mode 100644 index 0000000000000000000000000000000000000000..67cde9f676b7dbead6771de243a29cdf1b73f6be GIT binary patch literal 4702 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

9q^0& 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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/codemp/Debug/vssver.scc b/codemp/Debug/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..796ec67432a18940fc93953511fdb913f350bacd GIT binary patch literal 64 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiXzpMI5L3Iha81yU;eP8?!nedE9gWKRR)v{tsz J>fR|}J^;@l6KVhe literal 0 HcmV?d00001 diff --git a/codemp/EaxMan.dll b/codemp/EaxMan.dll new file mode 100644 index 0000000000000000000000000000000000000000..651cbfe731b275f324c76623a0530f57abb51ab5 GIT binary patch literal 94208 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 V0|5*KFc82%00RLG{6B?({{dt;YKZ^< literal 0 HcmV?d00001 diff --git a/codemp/JKA_mp.sln b/codemp/JKA_mp.sln new file mode 100644 index 0000000..d784d34 --- /dev/null +++ b/codemp/JKA_mp.sln @@ -0,0 +1,176 @@ +Microsoft Visual Studio Solution File, Format Version 7.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JK2cgame", "cgame\JK2_cgame.vcproj", "{4DC69D68-9DB8-4DEB-AC63-481153B1F41D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JK2game", "game\JK2_game.vcproj", "{5B587283-8429-4F8B-AECA-C09BF364B617}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinDed", "WinDed.vcproj", "{149BAC28-9F08-4C08-BD4E-02D01A0DAD1F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "botlib", "botlib\botlib.vcproj", "{07CCC82A-B905-4384-8AC5-E8A8E352B14C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exe", "jk2mp.vcproj", "{36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ui", "ui\ui.vcproj", "{2DDA2041-6A68-4754-B342-14039CC91695}" +EndProject +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(SourceCodeControl) = preSolution + SccNumberOfProjects = 6 + SccProjectUniqueName0 = cgame\\JK2_cgame.vcproj + SccProjectName0 = \u0022$/jedi/codemp/cgame\u0022,\u0020ICAAAAAA + SccLocalPath0 = cgame + SccProvider0 = MSSCCI:Microsoft\u0020Visual\u0020SourceSafe + CanCheckoutShared = true + SccProjectUniqueName1 = game\\JK2_game.vcproj + SccProjectName1 = \u0022$/jedi/codemp/game\u0022,\u0020EGAAAAAA + SccLocalPath1 = game + SccProvider1 = MSSCCI:Microsoft\u0020Visual\u0020SourceSafe + CanCheckoutShared = true + SccProjectUniqueName2 = WinDed.vcproj + SccProjectName2 = \u0022$/jedi/codemp\u0022,\u0020CAAAAAAA + SccLocalPath2 = . + SccProvider2 = MSSCCI:Microsoft\u0020Visual\u0020SourceSafe + CanCheckoutShared = true + SccProjectUniqueName3 = botlib\\botlib.vcproj + SccProjectName3 = \u0022$/jedi/codemp/botlib\u0022,\u0020EAAAAAAA + SccLocalPath3 = botlib + SccProvider3 = MSSCCI:Microsoft\u0020Visual\u0020SourceSafe + CanCheckoutShared = true + SccProjectUniqueName4 = jk2mp.vcproj + SccProjectName4 = \u0022$/jedi/codemp\u0022,\u0020CAAAAAAA + SccLocalPath4 = . + SccProvider4 = MSSCCI:Microsoft\u0020Visual\u0020SourceSafe + CanCheckoutShared = true + SccProjectUniqueName5 = ui\\ui.vcproj + SccProjectName5 = \u0022$/jedi/codemp/ui\u0022,\u0020YSAAAAAA + SccLocalPath5 = ui + SccProvider5 = MSSCCI:Microsoft\u0020Visual\u0020SourceSafe + CanCheckoutShared = true + EndGlobalSection + GlobalSection(SolutionConfiguration) = preSolution + ConfigName.0 = Debug + ConfigName.1 = Debug(SH) + ConfigName.2 = Final + ConfigName.3 = Release + EndGlobalSection + GlobalSection(ProjectDependencies) = postSolution + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.0 = {5B587283-8429-4F8B-AECA-C09BF364B617} + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.1 = {4DC69D68-9DB8-4DEB-AC63-481153B1F41D} + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.2 = {2DDA2041-6A68-4754-B342-14039CC91695} + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.3 = {07CCC82A-B905-4384-8AC5-E8A8E352B14C} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.0 = {0959DD50-BCBA-4551-9617-E6FD4DF85298} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.1 = {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.2 = {C79C9037-8F32-4833-A276-96B2FB96CCA5} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.3 = {A0451C0A-0B2F-400A-BBB1-98F517FAB30B} + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.4 = {613EA919-780E-416D-90A9-67D3EB27F899} + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {4DC69D68-9DB8-4DEB-AC63-481153B1F41D}.Debug.ActiveCfg = Debug|Win32 + {4DC69D68-9DB8-4DEB-AC63-481153B1F41D}.Debug.Build.0 = Debug|Win32 + {4DC69D68-9DB8-4DEB-AC63-481153B1F41D}.Debug(SH).ActiveCfg = Debug(SH)|Win32 + {4DC69D68-9DB8-4DEB-AC63-481153B1F41D}.Debug(SH).Build.0 = Debug(SH)|Win32 + {4DC69D68-9DB8-4DEB-AC63-481153B1F41D}.Final.ActiveCfg = Final|Win32 + {4DC69D68-9DB8-4DEB-AC63-481153B1F41D}.Final.Build.0 = Final|Win32 + {4DC69D68-9DB8-4DEB-AC63-481153B1F41D}.Release.ActiveCfg = Release|Win32 + {4DC69D68-9DB8-4DEB-AC63-481153B1F41D}.Release.Build.0 = Release|Win32 + {5B587283-8429-4F8B-AECA-C09BF364B617}.Debug.ActiveCfg = Debug|Win32 + {5B587283-8429-4F8B-AECA-C09BF364B617}.Debug.Build.0 = Debug|Win32 + {5B587283-8429-4F8B-AECA-C09BF364B617}.Debug(SH).ActiveCfg = Debug(SH)|Win32 + {5B587283-8429-4F8B-AECA-C09BF364B617}.Debug(SH).Build.0 = Debug(SH)|Win32 + {5B587283-8429-4F8B-AECA-C09BF364B617}.Final.ActiveCfg = Final|Win32 + {5B587283-8429-4F8B-AECA-C09BF364B617}.Final.Build.0 = Final|Win32 + {5B587283-8429-4F8B-AECA-C09BF364B617}.Release.ActiveCfg = Release|Win32 + {5B587283-8429-4F8B-AECA-C09BF364B617}.Release.Build.0 = Release|Win32 + {149BAC28-9F08-4C08-BD4E-02D01A0DAD1F}.Debug.ActiveCfg = Debug|Win32 + {149BAC28-9F08-4C08-BD4E-02D01A0DAD1F}.Debug(SH).ActiveCfg = Debug|Win32 + {149BAC28-9F08-4C08-BD4E-02D01A0DAD1F}.Final.ActiveCfg = Debug|Win32 + {149BAC28-9F08-4C08-BD4E-02D01A0DAD1F}.Release.ActiveCfg = Release|Win32 + {07CCC82A-B905-4384-8AC5-E8A8E352B14C}.Debug.ActiveCfg = Debug|Win32 + {07CCC82A-B905-4384-8AC5-E8A8E352B14C}.Debug.Build.0 = Debug|Win32 + {07CCC82A-B905-4384-8AC5-E8A8E352B14C}.Debug(SH).ActiveCfg = Debug(SH)|Win32 + {07CCC82A-B905-4384-8AC5-E8A8E352B14C}.Debug(SH).Build.0 = Debug(SH)|Win32 + {07CCC82A-B905-4384-8AC5-E8A8E352B14C}.Final.ActiveCfg = Final|Win32 + {07CCC82A-B905-4384-8AC5-E8A8E352B14C}.Final.Build.0 = Final|Win32 + {07CCC82A-B905-4384-8AC5-E8A8E352B14C}.Release.ActiveCfg = Release|Win32 + {07CCC82A-B905-4384-8AC5-E8A8E352B14C}.Release.Build.0 = Release|Win32 + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.Debug.ActiveCfg = Debug|Win32 + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.Debug.Build.0 = Debug|Win32 + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.Debug(SH).ActiveCfg = Debug(SH)|Win32 + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.Debug(SH).Build.0 = Debug(SH)|Win32 + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.Final.ActiveCfg = Final|Win32 + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.Final.Build.0 = Final|Win32 + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.Release.ActiveCfg = Release|Win32 + {36D7BB4B-5943-4477-AD6A-7B96CC0AB71A}.Release.Build.0 = Release|Win32 + {2DDA2041-6A68-4754-B342-14039CC91695}.Debug.ActiveCfg = Debug|Win32 + {2DDA2041-6A68-4754-B342-14039CC91695}.Debug.Build.0 = Debug|Win32 + {2DDA2041-6A68-4754-B342-14039CC91695}.Debug(SH).ActiveCfg = Debug(SH)|Win32 + {2DDA2041-6A68-4754-B342-14039CC91695}.Debug(SH).Build.0 = Debug(SH)|Win32 + {2DDA2041-6A68-4754-B342-14039CC91695}.Final.ActiveCfg = Final|Win32 + {2DDA2041-6A68-4754-B342-14039CC91695}.Final.Build.0 = Final|Win32 + {2DDA2041-6A68-4754-B342-14039CC91695}.Release.ActiveCfg = Release|Win32 + {2DDA2041-6A68-4754-B342-14039CC91695}.Release.Build.0 = Release|Win32 + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Debug.ActiveCfg = Debug|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Debug.Build.0 = Debug|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Debug(SH).ActiveCfg = Debug|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Final.ActiveCfg = Release|Xbox + {ACB93D0D-F6CB-480B-9B20-CE6C5EDC13BD}.Final.Build.0 = Release|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}.Debug(SH).ActiveCfg = Debug|Xbox + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.Final.ActiveCfg = Release|Xbox + {EEDF772D-5D2B-4C0F-B5C8-0A5828917847}.Final.Build.0 = Release|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}.Debug(SH).ActiveCfg = Debug|Xbox + {C79C9037-8F32-4833-A276-96B2FB96CCA5}.Final.ActiveCfg = Release|Xbox + {C79C9037-8F32-4833-A276-96B2FB96CCA5}.Final.Build.0 = Release|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}.Debug(SH).ActiveCfg = Debug|Xbox + {A0451C0A-0B2F-400A-BBB1-98F517FAB30B}.Final.ActiveCfg = Release|Xbox + {A0451C0A-0B2F-400A-BBB1-98F517FAB30B}.Final.Build.0 = Release|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}.Debug(SH).ActiveCfg = Debug|Xbox + {613EA919-780E-416D-90A9-67D3EB27F899}.Final.ActiveCfg = Release|Xbox + {613EA919-780E-416D-90A9-67D3EB27F899}.Final.Build.0 = Release|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}.Debug(SH).ActiveCfg = Debug|Xbox + {0959DD50-BCBA-4551-9617-E6FD4DF85298}.Final.ActiveCfg = Release|Xbox + {0959DD50-BCBA-4551-9617-E6FD4DF85298}.Final.Build.0 = Release|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/JKA_mp.vssscc b/codemp/JKA_mp.vssscc new file mode 100644 index 0000000..794f014 --- /dev/null +++ b/codemp/JKA_mp.vssscc @@ -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/codemp/OpenAL32.dll b/codemp/OpenAL32.dll new file mode 100644 index 0000000000000000000000000000000000000000..1b2c5536d900e25b4ae3cc9cded2429fe770d373 GIT binary patch literal 221184 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 mAn=IN_E~z8s-?19GF}U^Dd>k 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

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 zcmd5@O>A665-ukp2_ZlT|49fF$BFIyIR2Tj9Vc<@c$~zPC$}BTxxb;o!O@Y?(b2JSS3aoOk-}hJ-Co^BM4u4t{*h?! z9irSY(ZNAQ-8YB?4K69_##K=7Ek%9vM1uBxpy@WQpaTnv`hO)7H1dU_p*|u(N556n z{}IYT!wpTFaTWA~qLK5c2Riberk%J70*0WYFDp9uDeML9A5b**0g<2~$OMhwRCIij zNKk%{qT`=|2Mt`;G=VGV7%(Kgiz}#bRnvF4g7WWc`WaWy*e{AkKPM72woB3IIC#)F z>I*vXjiUX+R?y(rih8#qo}j+36dhV65_A~02hDIpK!?6pbQpFEI)=Uz)YH&( z3s*s#6iq`eXzF7{r_hF=X_bm|V;1D*U*(DL%!#f8gPmX?dNi!)c|7M3nwS-yNNs7=q04Ab)R zr5EStqZ;&QqoNd+&cAr+!g6tXX&O~3Z(dtoE>+J3wKHq0t3jhl9%nR)K{aRvOvw`% zy}72+5$%Ncrc{*`ORbV9=bX_v7qpakRap{p5siwY0d98s_o!~XG90dpj+AkwDoUqk zMo{hSTCLow)N4znSF3?&IjMx%G23X=8`=teZ%P4TOT&9pI@g?iyA{-$z*}ep&7cOW zMZBU?Qv((*H0tH)l@;jgUUDT&bGEkj=1ZmOn)CxT8E>@aD@_;>G-Q|=x>)H;wQ_K=z7kXg zE_#p)j9z(ht%<$F%1j;6d|L)PBC@*TS%l{h*Y!nK7ooL}!g9#i^6>Fks+L2>mZM4- zG|_#{qk+dLHwc=(l-CG*2e8l$d8;*7t2iw>yw#e0+tqS-t2NUIO09s~=Si$) zF=)0L^>?_cFS5GAN7Y}=V(?a_EEhN7jW4w|E|&gatkBUBW1<`j!bO=wDTCzyP&HDqVGWOqx@&kFR1tV zDA6ul$8o)i`rp8oPtoSruyrf!{0eri9zr{ZiCW;hVb}NIuY>;)Z8mz~4`>spF-`O_ zGMnyGsEhkICXuO}B>E8iP29h70(KzNx()gc_g|exh6(*0;ETAw1HCVy{|Vx^jUJ*d z+90o6=mFYIgLH~c&`H`($7mDnqG1}Pt@I#`&=@^N4^y7TsX)i+X?lvDp=W6a?Wb<) zp#wBQn`ww1rRV5z+DT8)lk^DXsE_*TAobEwI!s4s4;`Wb+Dp&VKANX;W znxZ0IqBAs07wHAMK&NS%Ch7mtrj216lcWPy6ujW#fO%pTmfsPn+Mj4UD1OW5s3 zwg(AMn|2>;W@i6ZHfHuNMxST2Or|zX*wcryaCZ`wxsQLFm~us#HnZ(2q8qb+iQ7$t z>k_xd%OlZTbib3l^>7@EKGD#c$#s%aC#MCYEH?1WX(M*sj$M&~sABHnDIrTIt)hpe z6kQ~L0}wrEh}nmxJ!8TQ%y?;Bi4N@An3h-pa~hBBPRPYWUqu-KL3cM|RHo{Y8S_-z@6UE)?rlzH1q z+ZxJZ=C9b8coR5bYYD~J`6AT);a!g3ccu;K_V;tvBVi8T(%XXQ;Em)3?pdBW3Q_B& zJV5cD7BUH8870q0P?nGdg}F`g=^RRI7R#Rj@{CiBHgkGr^H6!h;b(|)U6-dW=n>@! z2BlOr>pNpGfF;MqgyPu=phw;>9In81vITlFLl6~y7bUp>5#m5YaAqgA_$SQ zuBnX2U(vMGTg#Nx8L@2rQl)$_ib8KJ@zn|yoOMd@F$dZZ$K{$3^RyDIzaBxvhk(BZ zCBNk`uRjJ>s#TEz{?;45vEdQ@uJ7u$`L2z0^QZru_^Auo4>FirL|qScZN^>75ASb~ z+P!zi2kG$GTa>f5a$-qkR>e_2Piw0^2H&yu0e^a#M%s^!YFLHX2$Ex2G zxcn7*f7iOg$0-`Tq4ki9s^25H{F~_s=@jK|AY{np9uzEX=bhF^kMGz5p$Zz(XWZY- z?YvkiH|ov$YAe@wr9U@6cjSvkeApo8!{4NN!bs|h8!s!eDE!053(h>$PmGtp7)D>kc)3sGpq;*b4rV02 zN9{On<3lWNY909(08~fE$L+&NJnTHf8ISsj;rI{3=qsCNI0I5Yq4!V2=<7X>Z#yx? zd0EOU=#EEY-UKx#zP}KYbiPZh&`(Max7yPZ-dl7}QM&LX5bv7tG@^_sV|svizU+oC z8w858ug->5FdwjZSfYD2DlGS;$ARNaJ8MQ7+9r;swn9xeke#CC^(k%6^X(b<_JBsy zZ?JgN4ro@dHd`E)%-lD$EgH?ql(M0jMc#{H4l&&7pxHDm8{YBwvEK{42NhnznV$pO z`OG$(JI~LjfxAuPsthtCPRiVVNa3*j^!{OOw^*@Lt@HS$`1P>CvS+Fp{j%TSxoiC% z^wlFi)?3bWc9?eNc0R?gM?)-qERfN!6s#Q{R>kpT9Z#~+_<6PqF?`Ho=_3utEzV?( z;7$+8>#WX4VZ!PmHgJzytT4CbzN{mLh4>gg3$!OfG{rK}jw_*fbJO55W+Yq88377nW%*ErKw6jMh0t_Ye3*75vG z^KEw&En_6}DZcHApk-YV3key-^O()gSu}pq#rx{sj>)|iPd%M8kCK!p%kv&q)-jS# znQJ*8E9(eJ@oS%lm34gNQ&z_A5KDc!9al; z%bUtPn(g#inpgcEk~fj>saJ!r>{ZIBIINKD3kBZxy&Zo?G?M(7++W^1k6y}} zdz2%o_hF7(oM~%Rq&$@eB3PNnMal|)%s~^c^0+VS=&zJ04*FQ0V;s4iD5kB4LmbO_ z3&~N|;a^J64RcKWjx!rt+Fo%aik2~+X=_!UqsiB^**r_z;S_wVjD8go#y>vYl;1?+ zELc6kyf@1kJxn<{9pfHmeZr)y#p4c+7sk$4$P|x`bEK?Kmy|g+p>Uk{`Pup@EoV7l z(3~&KGvcM32A|Y;-nR%2AZOZHCDSs%QyPzd=)l|We0@w?lP5JAK6davqqu*Vrx~XE zkw!+$ly^Q;A!foc8OLs`4^#H5(+n+oOx7_{%G^5>BH52uc>C)ZEh%$hCPYg-FyQEI z#WCSYAl?cVLp=Q{7H_?C-_qvYtj6MB^<+aUB#e+Acy`vHMZXzxKsnP+G}AE8Y0Q`p zk~oaB+iV9jZQpQSV+sKM?=qRN?8h12`>Y&dI;XL`PpLBDrJatvpz-3q7&Uwj75EC2ui literal 0 HcmV?d00001 diff --git a/codemp/RMG/RM_Area.cpp b/codemp/RMG/RM_Area.cpp new file mode 100644 index 0000000..ae4d536 --- /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..b731f47 --- /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..8b960b7 --- /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..0d698dc --- /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..3ed3fdf --- /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_InstanceFile.cpp b/codemp/RMG/RM_InstanceFile.cpp new file mode 100644 index 0000000..66fd0af --- /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..729722e --- /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_Instance_BSP.cpp b/codemp/RMG/RM_Instance_BSP.cpp new file mode 100644 index 0000000..f5a91d7 --- /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..b1c4be3 --- /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..79124f2 --- /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..50b89c7 --- /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..1012c8a --- /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..20bdad9 --- /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..9cf11e6 --- /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..a437d89 --- /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_Manager.cpp b/codemp/RMG/RM_Manager.cpp new file mode 100644 index 0000000..5928626 --- /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..b959788 --- /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..2f1cee0 --- /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", "

{?XOCbkokCo#Aiz9n)O_!{RIA^ z`2BRS(Q4XVurWWebN-{o#pHfJMVOK0jj>bu7nAXKBT;S4i1v%K7DuB$Lt3?y*O|y& zJ;?ZakS+7lZ`kNcJjnTaky3RIQIwMliFuZ8WIa8IA17I!#}e*Uk(ALe8Z=697-{^w^B0x0yu5jv{?41y;3rsLO5b`nRih9St&Vw z5iDO5?>_W0UrNSv4K`W0t^c5cN4pqqe($@+g|$iQjqN2cRp5JXr5RUB)_*A+Xz8NI ztez>I2$#X(AGxQv6MEdqc*@~fu`Xse?RDw*M+H39ye!gIWh)``Q3+@4*z>)ie7=N? zrwVo-_sYL-O^cYg8eTKVaWD;gC?@;Yz^7-0jLlZQpSbhp zZ@|N9RdUd?*D`YcCb;(6q>p`e!!mOGX4pN=dWn<85g9rEO*qWpUUHxOf!g~e}7{17>pC=#XFbvCJ4y= zh=ZG=qBo^B1&FNScsR+NbK7>&4%$cHyJzM$G6y4Nn*L1Qo(Ba`gA#XKRoMJ z4{(`$ay=#b48|Y*OCBin?n$`tb9h;sUH8D$*}ScA3l_zQ%?>%8%X5U}_wE+_+xV}M z8@|2f+rqcu0q-5mQ9GSQRd6e8;^QF9Ek5Mo2H%0}^2Z+tp}OR#3?rOmMT6DXpJf{K G`{X}@7iMw* literal 0 HcmV?d00001 diff --git a/codemp/game/w_force.c b/codemp/game/w_force.c new file mode 100644 index 0000000..87d793c --- /dev/null +++ b/codemp/game/w_force.c @@ -0,0 +1,5787 @@ +//#include "g_local.h" +#include "b_local.h" +#include "w_saber.h" +#include "ai_main.h" +#include "../ghoul2/G2.h" + +#define METROID_JUMP 1 + +//NEEDED FOR MIND-TRICK on NPCS========================================================= +extern void NPC_PlayConfusionSound( gentity_t *self ); +extern void NPC_Jedi_PlayConfusionSound( gentity_t *self ); +extern void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone ); +//NEEDED FOR MIND-TRICK on NPCS========================================================= +extern void Jedi_Decloak( gentity_t *self ); + +extern vmCvar_t g_saberRestrictForce; + +#include "../namespace_begin.h" +extern qboolean BG_FullBodyTauntAnim( int anim ); +#include "../namespace_end.h" + +extern bot_state_t *botstates[MAX_CLIENTS]; + +int speedLoopSound = 0; + +int rageLoopSound = 0; + +int protectLoopSound = 0; + +int absorbLoopSound = 0; + +int seeLoopSound = 0; + +int ysalamiriLoopSound = 0; + +#define FORCE_VELOCITY_DAMAGE 0 + +int ForceShootDrain( gentity_t *self ); + +gentity_t *G_PreDefSound(vec3_t org, int pdSound) +{ + gentity_t *te; + + te = G_TempEntity( org, EV_PREDEFSOUND ); + te->s.eventParm = pdSound; + VectorCopy(org, te->s.origin); + + return te; +} + +const int forcePowerMinRank[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS] = //0 == neutral +{ + { + 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 + }, + { + 10,//FP_HEAL,//instant + 0,//FP_LEVITATION,//hold/duration + 0,//FP_SPEED,//duration + 0,//FP_PUSH,//hold/duration + 0,//FP_PULL,//hold/duration + 10,//FP_TELEPATHY,//instant + 15,//FP_GRIP,//hold/duration + 10,//FP_LIGHTNING,//hold/duration + 15,//FP_RAGE,//duration + 15,//FP_PROTECT,//duration + 15,//FP_ABSORB,//duration + 10,//FP_TEAM_HEAL,//instant + 10,//FP_TEAM_FORCE,//instant + 10,//FP_DRAIN,//hold/duration + 5,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 0,//FP_SABER_DEFENSE, + 0//FP_SABERTHROW, + //NUM_FORCE_POWERS + }, + { + 10,//FP_HEAL,//instant + 0,//FP_LEVITATION,//hold/duration + 0,//FP_SPEED,//duration + 0,//FP_PUSH,//hold/duration + 0,//FP_PULL,//hold/duration + 10,//FP_TELEPATHY,//instant + 15,//FP_GRIP,//hold/duration + 10,//FP_LIGHTNING,//hold/duration + 15,//FP_RAGE,//duration + 15,//FP_PROTECT,//duration + 15,//FP_ABSORB,//duration + 10,//FP_TEAM_HEAL,//instant + 10,//FP_TEAM_FORCE,//instant + 10,//FP_DRAIN,//hold/duration + 5,//FP_SEE,//duration + 5,//FP_SABER_OFFENSE, + 5,//FP_SABER_DEFENSE, + 5//FP_SABERTHROW, + //NUM_FORCE_POWERS + }, + { + 10,//FP_HEAL,//instant + 0,//FP_LEVITATION,//hold/duration + 0,//FP_SPEED,//duration + 0,//FP_PUSH,//hold/duration + 0,//FP_PULL,//hold/duration + 10,//FP_TELEPATHY,//instant + 15,//FP_GRIP,//hold/duration + 10,//FP_LIGHTNING,//hold/duration + 15,//FP_RAGE,//duration + 15,//FP_PROTECT,//duration + 15,//FP_ABSORB,//duration + 10,//FP_TEAM_HEAL,//instant + 10,//FP_TEAM_FORCE,//instant + 10,//FP_DRAIN,//hold/duration + 5,//FP_SEE,//duration + 10,//FP_SABER_OFFENSE, + 10,//FP_SABER_DEFENSE, + 10//FP_SABERTHROW, + //NUM_FORCE_POWERS + } +}; + +const int mindTrickTime[NUM_FORCE_POWER_LEVELS] = +{ + 0,//none + 5000, + 10000, + 15000 +}; + +void WP_InitForcePowers( gentity_t *ent ) +{ + int i; + int i_r; + int maxRank = g_maxForceRank.integer; + qboolean warnClient = qfalse; + qboolean warnClientLimit = qfalse; + char userinfo[MAX_INFO_STRING]; + char forcePowers[256]; + char readBuf[256]; + int lastFPKnown = -1; + qboolean didEvent = qfalse; + + if (!maxRank) + { //if server has no max rank, default to max (50) + maxRank = FORCE_MASTERY_JEDI_MASTER; + } + + /* + if (g_forcePowerDisable.integer) + { + maxRank = FORCE_MASTERY_UNINITIATED; + } + */ + //rww - don't do this + + if ( !ent || !ent->client ) + { + return; + } + + ent->client->ps.fd.saberAnimLevel = ent->client->sess.saberLevel; + + if (ent->client->ps.fd.saberAnimLevel < FORCE_LEVEL_1 || + ent->client->ps.fd.saberAnimLevel > FORCE_LEVEL_3) + { + ent->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1; + } + + if (!speedLoopSound) + { //so that the client configstring is already modified with this when we need it + speedLoopSound = G_SoundIndex("sound/weapons/force/speedloop.wav"); + } + + if (!rageLoopSound) + { + rageLoopSound = G_SoundIndex("sound/weapons/force/rageloop.wav"); + } + + if (!absorbLoopSound) + { + absorbLoopSound = G_SoundIndex("sound/weapons/force/absorbloop.wav"); + } + + if (!protectLoopSound) + { + protectLoopSound = G_SoundIndex("sound/weapons/force/protectloop.wav"); + } + + if (!seeLoopSound) + { + seeLoopSound = G_SoundIndex("sound/weapons/force/seeloop.wav"); + } + + if (!ysalamiriLoopSound) + { + ysalamiriLoopSound = G_SoundIndex("sound/player/nullifyloop.wav"); + } + + if (ent->s.eType == ET_NPC) + { //just stop here then. + return; + } + + i = 0; + while (i < NUM_FORCE_POWERS) + { + ent->client->ps.fd.forcePowerLevel[i] = 0; + ent->client->ps.fd.forcePowersKnown &= ~(1 << i); + i++; + } + + ent->client->ps.fd.forcePowerSelected = -1; + + ent->client->ps.fd.forceSide = 0; + + if (g_gametype.integer == GT_SIEGE && + ent->client->siegeClass != -1) + { //Then use the powers for this class, and skip all this nonsense. + i = 0; + + while (i < NUM_FORCE_POWERS) + { + ent->client->ps.fd.forcePowerLevel[i] = bgSiegeClasses[ent->client->siegeClass].forcePowerLevels[i]; + + if (!ent->client->ps.fd.forcePowerLevel[i]) + { + ent->client->ps.fd.forcePowersKnown &= ~(1 << i); + } + else + { + ent->client->ps.fd.forcePowersKnown |= (1 << i); + } + i++; + } + + if (!ent->client->sess.setForce) + { + //bring up the class selection menu + trap_SendServerCommand(ent-g_entities, "scl"); + } + ent->client->sess.setForce = qtrue; + + return; + } + + if (ent->s.eType == ET_NPC && ent->s.number >= MAX_CLIENTS) + { //rwwFIXMEFIXME: Temp + strcpy(userinfo, "forcepowers\\7-1-333003000313003120"); + } + else + { + trap_GetUserinfo( ent->s.number, userinfo, sizeof( userinfo ) ); + } + + Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) ); + + if ( (ent->r.svFlags & SVF_BOT) && botstates[ent->s.number] ) + { //if it's a bot just copy the info directly from its personality + Com_sprintf(forcePowers, sizeof(forcePowers), "%s\0", botstates[ent->s.number]->forceinfo); + } + + //rww - parse through the string manually and eat out all the appropriate data + i = 0; + + if (g_forceBasedTeams.integer) + { + if (ent->client->sess.sessionTeam == TEAM_RED) + { + warnClient = !(BG_LegalizedForcePowers(forcePowers, maxRank, HasSetSaberOnly(), FORCE_DARKSIDE, g_gametype.integer, g_forcePowerDisable.integer)); + } + else if (ent->client->sess.sessionTeam == TEAM_BLUE) + { + warnClient = !(BG_LegalizedForcePowers(forcePowers, maxRank, HasSetSaberOnly(), FORCE_LIGHTSIDE, g_gametype.integer, g_forcePowerDisable.integer)); + } + else + { + warnClient = !(BG_LegalizedForcePowers(forcePowers, maxRank, HasSetSaberOnly(), 0, g_gametype.integer, g_forcePowerDisable.integer)); + } + } + else + { + warnClient = !(BG_LegalizedForcePowers(forcePowers, maxRank, HasSetSaberOnly(), 0, g_gametype.integer, g_forcePowerDisable.integer)); + } + + i_r = 0; + while (forcePowers[i] && forcePowers[i] != '-') + { + readBuf[i_r] = forcePowers[i]; + i_r++; + i++; + } + readBuf[i_r] = 0; + //THE RANK + ent->client->ps.fd.forceRank = atoi(readBuf); + i++; + + i_r = 0; + while (forcePowers[i] && forcePowers[i] != '-') + { + readBuf[i_r] = forcePowers[i]; + i_r++; + i++; + } + readBuf[i_r] = 0; + //THE SIDE + ent->client->ps.fd.forceSide = atoi(readBuf); + i++; + + + if ( g_gametype.integer != GT_SIEGE && (ent->r.svFlags & SVF_BOT) && botstates[ent->s.number] ) + { //hmm..I'm going to cheat here. + int oldI = i; + i_r = 0; + while (forcePowers[i] && forcePowers[i] != '\n' && + i_r < NUM_FORCE_POWERS) + { + if (ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + if (i_r == FP_ABSORB) + { + forcePowers[i] = '3'; + } + if (botstates[ent->s.number]->settings.skill >= 4) + { //cheat and give them more stuff + if (i_r == FP_HEAL) + { + forcePowers[i] = '3'; + } + else if (i_r == FP_PROTECT) + { + forcePowers[i] = '3'; + } + } + } + else if (ent->client->ps.fd.forceSide == FORCE_DARKSIDE) + { + if (botstates[ent->s.number]->settings.skill >= 4) + { + if (i_r == FP_GRIP) + { + forcePowers[i] = '3'; + } + else if (i_r == FP_LIGHTNING) + { + forcePowers[i] = '3'; + } + else if (i_r == FP_RAGE) + { + forcePowers[i] = '3'; + } + else if (i_r == FP_DRAIN) + { + forcePowers[i] = '3'; + } + } + } + + if (i_r == FP_PUSH) + { + forcePowers[i] = '3'; + } + else if (i_r == FP_PULL) + { + forcePowers[i] = '3'; + } + + i++; + i_r++; + } + i = oldI; + } + + i_r = 0; + while (forcePowers[i] && forcePowers[i] != '\n' && + i_r < NUM_FORCE_POWERS) + { + readBuf[0] = forcePowers[i]; + readBuf[1] = 0; + + ent->client->ps.fd.forcePowerLevel[i_r] = atoi(readBuf); + if (ent->client->ps.fd.forcePowerLevel[i_r]) + { + ent->client->ps.fd.forcePowersKnown |= (1 << i_r); + } + else + { + ent->client->ps.fd.forcePowersKnown &= ~(1 << i_r); + } + i++; + i_r++; + } + //THE POWERS + + if (ent->s.eType != ET_NPC) + { + if (HasSetSaberOnly()) + { + gentity_t *te = G_TempEntity( vec3_origin, EV_SET_FREE_SABER ); + te->r.svFlags |= SVF_BROADCAST; + te->s.eventParm = 1; + } + else + { + gentity_t *te = G_TempEntity( vec3_origin, EV_SET_FREE_SABER ); + te->r.svFlags |= SVF_BROADCAST; + te->s.eventParm = 0; + } + + if (g_forcePowerDisable.integer) + { + gentity_t *te = G_TempEntity( vec3_origin, EV_SET_FORCE_DISABLE ); + te->r.svFlags |= SVF_BROADCAST; + te->s.eventParm = 1; + } + else + { + gentity_t *te = G_TempEntity( vec3_origin, EV_SET_FORCE_DISABLE ); + te->r.svFlags |= SVF_BROADCAST; + te->s.eventParm = 0; + } + } + + //rww - It seems we currently want to always do this, even if the player isn't exceeding the max + //rank, so.. +// if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) +// { //totally messes duel up to force someone into spec mode, and besides, each "round" is + //counted as a full restart +// ent->client->sess.setForce = qtrue; +// } + + if (ent->s.eType == ET_NPC) + { + ent->client->sess.setForce = qtrue; + } + else if (g_gametype.integer == GT_SIEGE) + { + if (!ent->client->sess.setForce) + { + ent->client->sess.setForce = qtrue; + //bring up the class selection menu + trap_SendServerCommand(ent-g_entities, "scl"); + } + } + else + { + if (warnClient || !ent->client->sess.setForce) + { //the client's rank is too high for the server and has been autocapped, so tell them + if (g_gametype.integer != GT_HOLOCRON && g_gametype.integer != GT_JEDIMASTER) + { +#ifdef EVENT_FORCE_RANK + gentity_t *te = G_TempEntity( vec3_origin, EV_GIVE_NEW_RANK ); + + te->r.svFlags |= SVF_BROADCAST; + te->s.trickedentindex = ent->s.number; + te->s.eventParm = maxRank; + te->s.bolt1 = 0; +#endif + didEvent = qtrue; + +// if (!(ent->r.svFlags & SVF_BOT) && g_gametype.integer != GT_DUEL && g_gametype.integer != GT_POWERDUEL && ent->s.eType != ET_NPC) + if (!(ent->r.svFlags & SVF_BOT) && ent->s.eType != ET_NPC) + { + if (!g_teamAutoJoin.integer) + { + //Make them a spectator so they can set their powerups up without being bothered. + ent->client->sess.sessionTeam = TEAM_SPECTATOR; + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->client->sess.spectatorClient = 0; + + ent->client->pers.teamState.state = TEAM_BEGIN; + trap_SendServerCommand(ent-g_entities, "spc"); // Fire up the profile menu + } + } + +#ifdef EVENT_FORCE_RANK + te->s.bolt2 = ent->client->sess.sessionTeam; +#else + //Event isn't very reliable, I made it a string. This way I can send it to just one + //client also, as opposed to making a broadcast event. + trap_SendServerCommand(ent->s.number, va("nfr %i %i %i", maxRank, 1, ent->client->sess.sessionTeam)); + //Arg1 is new max rank, arg2 is non-0 if force menu should be shown, arg3 is the current team +#endif + } + ent->client->sess.setForce = qtrue; + } + + if (!didEvent) + { +#ifdef EVENT_FORCE_RANK + gentity_t *te = G_TempEntity( vec3_origin, EV_GIVE_NEW_RANK ); + + te->r.svFlags |= SVF_BROADCAST; + te->s.trickedentindex = ent->s.number; + te->s.eventParm = maxRank; + te->s.bolt1 = 1; + te->s.bolt2 = ent->client->sess.sessionTeam; +#else + trap_SendServerCommand(ent->s.number, va("nfr %i %i %i", maxRank, 0, ent->client->sess.sessionTeam)); +#endif + } + + if (warnClientLimit) + { //the server has one or more force powers disabled and the client is using them in his config + //trap_SendServerCommand(ent-g_entities, va("print \"The server has one or more force powers that you have chosen disabled.\nYou will not be able to use the disable force power(s) while playing on this server.\n\"")); + } + } + + i = 0; + while (i < NUM_FORCE_POWERS) + { + if ((ent->client->ps.fd.forcePowersKnown & (1 << i)) && + !ent->client->ps.fd.forcePowerLevel[i]) + { //err.. + ent->client->ps.fd.forcePowersKnown &= ~(1 << i); + } + else + { + if (i != FP_LEVITATION && i != FP_SABER_OFFENSE && i != FP_SABER_DEFENSE && i != FP_SABERTHROW) + { + lastFPKnown = i; + } + } + + i++; + } + + if (ent->client->ps.fd.forcePowersKnown & ent->client->sess.selectedFP) + { + ent->client->ps.fd.forcePowerSelected = ent->client->sess.selectedFP; + } + + if (!(ent->client->ps.fd.forcePowersKnown & (1 << ent->client->ps.fd.forcePowerSelected))) + { + if (lastFPKnown != -1) + { + ent->client->ps.fd.forcePowerSelected = lastFPKnown; + } + else + { + ent->client->ps.fd.forcePowerSelected = 0; + } + } + + while (i < NUM_FORCE_POWERS) + { + ent->client->ps.fd.forcePowerBaseLevel[i] = ent->client->ps.fd.forcePowerLevel[i]; + i++; + } + ent->client->ps.fd.forceUsingAdded = 0; +} + +void WP_SpawnInitForcePowers( gentity_t *ent ) +{ + int i = 0; + + ent->client->ps.saberAttackChainCount = 0; + + i = 0; + + while (i < NUM_FORCE_POWERS) + { + if (ent->client->ps.fd.forcePowersActive & (1 << i)) + { + WP_ForcePowerStop(ent, i); + } + + i++; + } + + ent->client->ps.fd.forceDeactivateAll = 0; + + ent->client->ps.fd.forcePower = ent->client->ps.fd.forcePowerMax = FORCE_POWER_MAX; + ent->client->ps.fd.forcePowerRegenDebounceTime = 0; + ent->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE; + ent->client->ps.fd.forceMindtrickTargetIndex = 0; + ent->client->ps.fd.forceMindtrickTargetIndex2 = 0; + ent->client->ps.fd.forceMindtrickTargetIndex3 = 0; + ent->client->ps.fd.forceMindtrickTargetIndex4 = 0; + + ent->client->ps.holocronBits = 0; + + i = 0; + while (i < NUM_FORCE_POWERS) + { + ent->client->ps.holocronsCarried[i] = 0; + i++; + } + + if (g_gametype.integer == GT_HOLOCRON) + { + i = 0; + while (i < NUM_FORCE_POWERS) + { + ent->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_0; + i++; + } + + if (HasSetSaberOnly()) + { + if (ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_1) + { + ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1; + } + if (ent->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_1) + { + ent->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1; + } + } + } + + i = 0; + + while (i < NUM_FORCE_POWERS) + { + ent->client->ps.fd.forcePowerDebounce[i] = 0; + ent->client->ps.fd.forcePowerDuration[i] = 0; + + i++; + } + + ent->client->ps.fd.forcePowerRegenDebounceTime = 0; + ent->client->ps.fd.forceJumpZStart = 0; + ent->client->ps.fd.forceJumpCharge = 0; + ent->client->ps.fd.forceJumpSound = 0; + ent->client->ps.fd.forceGripDamageDebounceTime = 0; + ent->client->ps.fd.forceGripBeingGripped = 0; + ent->client->ps.fd.forceGripCripple = 0; + ent->client->ps.fd.forceGripUseTime = 0; + ent->client->ps.fd.forceGripSoundTime = 0; + ent->client->ps.fd.forceGripStarted = 0; + ent->client->ps.fd.forceHealTime = 0; + ent->client->ps.fd.forceHealAmount = 0; + ent->client->ps.fd.forceRageRecoveryTime = 0; + ent->client->ps.fd.forceDrainEntNum = ENTITYNUM_NONE; + ent->client->ps.fd.forceDrainTime = 0; + + i = 0; + while (i < NUM_FORCE_POWERS) + { + if ((ent->client->ps.fd.forcePowersKnown & (1 << i)) && + !ent->client->ps.fd.forcePowerLevel[i]) + { //make sure all known powers are cleared if we have level 0 in them + ent->client->ps.fd.forcePowersKnown &= ~(1 << i); + } + + i++; + } + + if (g_gametype.integer == GT_SIEGE && + ent->client->siegeClass != -1) + { //Then use the powers for this class. + i = 0; + + while (i < NUM_FORCE_POWERS) + { + ent->client->ps.fd.forcePowerLevel[i] = bgSiegeClasses[ent->client->siegeClass].forcePowerLevels[i]; + + if (!ent->client->ps.fd.forcePowerLevel[i]) + { + ent->client->ps.fd.forcePowersKnown &= ~(1 << i); + } + else + { + ent->client->ps.fd.forcePowersKnown |= (1 << i); + } + i++; + } + } +} + +#include "../namespace_begin.h" +extern qboolean BG_InKnockDown( int anim ); //bg_pmove.c +#include "../namespace_end.h" + +int ForcePowerUsableOn(gentity_t *attacker, gentity_t *other, forcePowers_t forcePower) +{ + if (other && other->client && BG_HasYsalamiri(g_gametype.integer, &other->client->ps)) + { + return 0; + } + + if (attacker && attacker->client && !BG_CanUseFPNow(g_gametype.integer, &attacker->client->ps, level.time, forcePower)) + { + return 0; + } + + //Dueling fighters cannot use force powers on others, with the exception of force push when locked with each other + if (attacker && attacker->client && attacker->client->ps.duelInProgress) + { + return 0; + } + + if (other && other->client && other->client->ps.duelInProgress) + { + return 0; + } + + if (forcePower == FP_GRIP) + { + if (other && other->client && + (other->client->ps.fd.forcePowersActive & (1<client->forcePowerSoundDebounce < level.time) + { + gentity_t *abSound = G_PreDefSound(other->client->ps.origin, PDSOUND_ABSORBHIT); + abSound->s.trickedentindex = other->s.number; + other->client->forcePowerSoundDebounce = level.time + 400; + } + return 0; + } + else if (other && other->client && + other->client->ps.weapon == WP_SABER && + BG_SaberInSpecial(other->client->ps.saberMove)) + { //don't grip person while they are in a special or some really bad things can happen. + return 0; + } + } + + if (other && other->client && + (forcePower == FP_PUSH || + forcePower == FP_PULL)) + { + if (BG_InKnockDown(other->client->ps.legsAnim)) + { + return 0; + } + } + + if (other && other->client && other->s.eType == ET_NPC && + other->s.NPC_class == CLASS_VEHICLE) + { //can't use the force on vehicles.. except lightning + if (forcePower == FP_LIGHTNING) + { + return 1; + } + else + { + return 0; + } + } + + if (other && other->client && other->s.eType == ET_NPC && + g_gametype.integer == GT_SIEGE) + { //can't use powers at all on npc's normally in siege... + return 0; + } + + return 1; +} + +qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ) +{ + int drain = overrideAmt ? overrideAmt : + forcePowerNeeded[self->client->ps.fd.forcePowerLevel[forcePower]][forcePower]; + + if (self->client->ps.fd.forcePowersActive & (1 << forcePower)) + { //we're probably going to deactivate it.. + return qtrue; + } + if ( forcePower == FP_LEVITATION ) + { + return qtrue; + } + if ( !drain ) + { + return qtrue; + } + if ((forcePower == FP_DRAIN || forcePower == FP_LIGHTNING) && + self->client->ps.fd.forcePower >= 25) + { //it's ok then, drain/lightning are actually duration + return qtrue; + } + if ( self->client->ps.fd.forcePower < drain ) + { + return qfalse; + } + return qtrue; +} + +qboolean WP_ForcePowerInUse( gentity_t *self, forcePowers_t forcePower ) +{ + if ( (self->client->ps.fd.forcePowersActive & ( 1 << forcePower )) ) + {//already using this power + return qtrue; + } + + return qfalse; +} + +qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower ) +{ + if (BG_HasYsalamiri(g_gametype.integer, &self->client->ps)) + { + return qfalse; + } + + if (self->health <= 0 || self->client->ps.stats[STAT_HEALTH] <= 0 || + (self->client->ps.eFlags & EF_DEAD)) + { + return qfalse; + } + + if (self->client->ps.pm_flags & PMF_FOLLOW) + { //specs can't use powers through people + return qfalse; + } + if (self->client->sess.sessionTeam == TEAM_SPECTATOR) + { + return qfalse; + } + if (self->client->tempSpectate >= level.time) + { + return qfalse; + } + + if (!BG_CanUseFPNow(g_gametype.integer, &self->client->ps, level.time, forcePower)) + { + return qfalse; + } + + if ( !(self->client->ps.fd.forcePowersKnown & ( 1 << forcePower )) ) + {//don't know this power + return qfalse; + } + + if ( (self->client->ps.fd.forcePowersActive & ( 1 << forcePower )) ) + {//already using this power + if (forcePower != FP_LEVITATION) + { + return qfalse; + } + } + + if (forcePower == FP_LEVITATION && self->client->fjDidJump) + { + return qfalse; + } + + if (!self->client->ps.fd.forcePowerLevel[forcePower]) + { + return qfalse; + } + + if ( g_debugMelee.integer ) + { + if ( (self->client->ps.pm_flags&PMF_STUCK_TO_WALL) ) + {//no offensive force powers when stuck to wall + switch ( forcePower ) + { + case FP_GRIP: + case FP_LIGHTNING: + case FP_DRAIN: + case FP_SABER_OFFENSE: + case FP_SABER_DEFENSE: + case FP_SABERTHROW: + return qfalse; + break; + } + } + } + + if ( !self->client->ps.saberHolstered ) + { + if ( self->client->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->saber[0].twoHanded + || (self->client->saber[0].model && self->client->saber[0].model[0]) ) + {//this saber requires the use of two hands OR our other hand is using an active saber too + if ( (self->client->saber[0].forceRestrictions&(1<client->saber[0].model + && self->client->saber[0].model[0] ) + {//both sabers on + 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->saber[1].forceRestrictions&(1<client->ps.fd.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.fd.forcePowerLevel[FP_ABSORB]; + + if (addTot < 1 && atForceSpent >= 1) + { + addTot = 1; + } + attacked->client->ps.fd.forcePower += addTot; + if (attacked->client->ps.fd.forcePower > 100) + { + attacked->client->ps.fd.forcePower = 100; + } + + //play sound indicating that attack was absorbed + if (attacked->client->forcePowerSoundDebounce < level.time) + { + abSound = G_PreDefSound(attacked->client->ps.origin, PDSOUND_ABSORBHIT); + abSound->s.trickedentindex = attacked->s.number; + + attacked->client->forcePowerSoundDebounce = level.time + 400; + } + + return getLevel; +} + +void WP_ForcePowerRegenerate( gentity_t *self, int overrideAmt ) +{ //called on a regular interval to regenerate force power. + if ( !self->client ) + { + return; + } + + if ( overrideAmt ) + { //custom regen amount + self->client->ps.fd.forcePower += overrideAmt; + } + else + { //otherwise, just 1 + self->client->ps.fd.forcePower++; + } + + if ( self->client->ps.fd.forcePower > self->client->ps.fd.forcePowerMax ) + { //cap it off at the max (default 100) + self->client->ps.fd.forcePower = self->client->ps.fd.forcePowerMax; + } +} + +void WP_ForcePowerStart( gentity_t *self, forcePowers_t forcePower, int overrideAmt ) +{ //activate the given force power + int duration = 0; + qboolean hearable = qfalse; + float hearDist = 0; + + if (!WP_ForcePowerAvailable( self, forcePower, overrideAmt )) + { + return; + } + + if ( BG_FullBodyTauntAnim( self->client->ps.legsAnim ) ) + {//stop taunt + self->client->ps.legsTimer = 0; + } + if ( BG_FullBodyTauntAnim( self->client->ps.torsoAnim ) ) + {//stop taunt + self->client->ps.torsoTimer = 0; + } + //hearable and hearDist are merely for the benefit of bots, and not related to if a sound is actually played. + //If duration is set, the force power will assume to be timer-based. + switch( (int)forcePower ) + { + case FP_HEAL: + hearable = qtrue; + hearDist = 256; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_LEVITATION: + hearable = qtrue; + hearDist = 256; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_SPEED: + hearable = qtrue; + hearDist = 256; + if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_1) + { + duration = 10000; + } + else if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_2) + { + duration = 15000; + } + else if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_3) + { + duration = 20000; + } + else //shouldn't get here + { + break; + } + + if (overrideAmt) + { + duration = overrideAmt; + } + + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_PUSH: + hearable = qtrue; + hearDist = 256; + break; + case FP_PULL: + hearable = qtrue; + hearDist = 256; + break; + case FP_TELEPATHY: + hearable = qtrue; + hearDist = 256; + if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_1) + { + duration = 20000; + } + else if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_2) + { + duration = 25000; + } + else if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_3) + { + duration = 30000; + } + else //shouldn't get here + { + break; + } + + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_GRIP: + hearable = qtrue; + hearDist = 256; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + self->client->ps.powerups[PW_DISINT_4] = level.time + 60000; + break; + case FP_LIGHTNING: + hearable = qtrue; + hearDist = 512; + duration = overrideAmt; + overrideAmt = 0; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_LIGHTNING]; + break; + case FP_RAGE: + hearable = qtrue; + hearDist = 256; + if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1) + { + duration = 8000; + } + else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2) + { + duration = 14000; + } + else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3) + { + duration = 20000; + } + else //shouldn't get here + { + break; + } + + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_PROTECT: + hearable = qtrue; + hearDist = 256; + duration = 20000; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_ABSORB: + hearable = qtrue; + hearDist = 256; + duration = 20000; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_TEAM_HEAL: + hearable = qtrue; + hearDist = 256; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_TEAM_FORCE: + hearable = qtrue; + hearDist = 256; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_DRAIN: + hearable = qtrue; + hearDist = 256; + duration = overrideAmt; + overrideAmt = 0; + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + //self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_DRAIN]; + break; + case FP_SEE: + hearable = qtrue; + hearDist = 256; + if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_1) + { + duration = 10000; + } + else if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_2) + { + duration = 20000; + } + else if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_3) + { + duration = 30000; + } + else //shouldn't get here + { + break; + } + + self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); + break; + case FP_SABER_OFFENSE: + break; + case FP_SABER_DEFENSE: + break; + case FP_SABERTHROW: + break; + default: + break; + } + + if ( duration ) + { + self->client->ps.fd.forcePowerDuration[forcePower] = level.time + duration; + } + else + { + self->client->ps.fd.forcePowerDuration[forcePower] = 0; + } + + if (hearable) + { + self->client->ps.otherSoundLen = hearDist; + self->client->ps.otherSoundTime = level.time + 100; + } + + self->client->ps.fd.forcePowerDebounce[forcePower] = 0; + + if ((int)forcePower == FP_SPEED && overrideAmt) + { + BG_ForcePowerDrain( &self->client->ps, forcePower, overrideAmt*0.025 ); + } + else if ((int)forcePower != FP_GRIP && (int)forcePower != FP_DRAIN) + { //grip and drain drain as damage is done + BG_ForcePowerDrain( &self->client->ps, forcePower, overrideAmt ); + } +} + +void ForceHeal( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if ( !WP_ForcePowerUsable( self, FP_HEAL ) ) + { + return; + } + + if ( self->health >= self->client->ps.stats[STAT_MAX_HEALTH]) + { + return; + } + + if (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_3) + { + self->health += 25; //This was 50, but that angered the Balance God. + + if (self->health > self->client->ps.stats[STAT_MAX_HEALTH]) + { + self->health = self->client->ps.stats[STAT_MAX_HEALTH]; + } + BG_ForcePowerDrain( &self->client->ps, FP_HEAL, 0 ); + } + else if (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_2) + { + self->health += 10; + + if (self->health > self->client->ps.stats[STAT_MAX_HEALTH]) + { + self->health = self->client->ps.stats[STAT_MAX_HEALTH]; + } + BG_ForcePowerDrain( &self->client->ps, FP_HEAL, 0 ); + } + else + { + self->health += 5; + + if (self->health > self->client->ps.stats[STAT_MAX_HEALTH]) + { + self->health = self->client->ps.stats[STAT_MAX_HEALTH]; + } + BG_ForcePowerDrain( &self->client->ps, FP_HEAL, 0 ); + } + /* + else + { + WP_ForcePowerStart( self, FP_HEAL, 0 ); + } + */ + //NOTE: Decided to make all levels instant. + + G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/weapons/force/heal.wav") ); +} + +void WP_AddToClientBitflags(gentity_t *ent, int entNum) +{ + if (!ent) + { + return; + } + + if (entNum > 47) + { + ent->s.trickedentindex4 |= (1 << (entNum-48)); + } + else if (entNum > 31) + { + ent->s.trickedentindex3 |= (1 << (entNum-32)); + } + else if (entNum > 15) + { + ent->s.trickedentindex2 |= (1 << (entNum-16)); + } + else + { + ent->s.trickedentindex |= (1 << entNum); + } +} + +void ForceTeamHeal( gentity_t *self ) +{ + float radius = 256; + int i = 0; + gentity_t *ent; + vec3_t a; + int numpl = 0; + int pl[MAX_CLIENTS]; + int healthadd = 0; + gentity_t *te = NULL; + + if ( self->health <= 0 ) + { + return; + } + + if ( !WP_ForcePowerUsable( self, FP_TEAM_HEAL ) ) + { + return; + } + + if (self->client->ps.fd.forcePowerDebounce[FP_TEAM_HEAL] >= level.time) + { + return; + } + + if (self->client->ps.fd.forcePowerLevel[FP_TEAM_HEAL] == FORCE_LEVEL_2) + { + radius *= 1.5; + } + if (self->client->ps.fd.forcePowerLevel[FP_TEAM_HEAL] == FORCE_LEVEL_3) + { + radius *= 2; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && self != ent && OnSameTeam(self, ent) && ent->client->ps.stats[STAT_HEALTH] < ent->client->ps.stats[STAT_MAX_HEALTH] && ent->client->ps.stats[STAT_HEALTH] > 0 && ForcePowerUsableOn(self, ent, FP_TEAM_HEAL) && + trap_InPVS(self->client->ps.origin, ent->client->ps.origin)) + { + VectorSubtract(self->client->ps.origin, ent->client->ps.origin, a); + + if (VectorLength(a) <= radius) + { + pl[numpl] = i; + numpl++; + } + } + + i++; + } + + if (numpl < 1) + { + return; + } + + if (numpl == 1) + { + healthadd = 50; + } + else if (numpl == 2) + { + healthadd = 33; + } + else + { + healthadd = 25; + } + + self->client->ps.fd.forcePowerDebounce[FP_TEAM_HEAL] = level.time + 2000; + i = 0; + + while (i < numpl) + { + if (g_entities[pl[i]].client->ps.stats[STAT_HEALTH] > 0 && + g_entities[pl[i]].health > 0) + { + g_entities[pl[i]].client->ps.stats[STAT_HEALTH] += healthadd; + if (g_entities[pl[i]].client->ps.stats[STAT_HEALTH] > g_entities[pl[i]].client->ps.stats[STAT_MAX_HEALTH]) + { + g_entities[pl[i]].client->ps.stats[STAT_HEALTH] = g_entities[pl[i]].client->ps.stats[STAT_MAX_HEALTH]; + } + + g_entities[pl[i]].health = g_entities[pl[i]].client->ps.stats[STAT_HEALTH]; + + //At this point we know we got one, so add him into the collective event client bitflag + if (!te) + { + te = G_TempEntity( self->client->ps.origin, EV_TEAM_POWER); + te->s.eventParm = 1; //eventParm 1 is heal, eventParm 2 is force regen + + //since we had an extra check above, do the drain now because we got at least one guy + BG_ForcePowerDrain( &self->client->ps, FP_TEAM_HEAL, forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL] ); + } + + WP_AddToClientBitflags(te, pl[i]); + //Now cramming it all into one event.. doing this many g_sound events at once was a Bad Thing. + } + i++; + } +} + +void ForceTeamForceReplenish( gentity_t *self ) +{ + float radius = 256; + int i = 0; + gentity_t *ent; + vec3_t a; + int numpl = 0; + int pl[MAX_CLIENTS]; + int poweradd = 0; + gentity_t *te = NULL; + + if ( self->health <= 0 ) + { + return; + } + + if ( !WP_ForcePowerUsable( self, FP_TEAM_FORCE ) ) + { + return; + } + + if (self->client->ps.fd.forcePowerDebounce[FP_TEAM_FORCE] >= level.time) + { + return; + } + + if (self->client->ps.fd.forcePowerLevel[FP_TEAM_FORCE] == FORCE_LEVEL_2) + { + radius *= 1.5; + } + if (self->client->ps.fd.forcePowerLevel[FP_TEAM_FORCE] == FORCE_LEVEL_3) + { + radius *= 2; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && self != ent && OnSameTeam(self, ent) && ent->client->ps.fd.forcePower < 100 && ForcePowerUsableOn(self, ent, FP_TEAM_FORCE) && + trap_InPVS(self->client->ps.origin, ent->client->ps.origin)) + { + VectorSubtract(self->client->ps.origin, ent->client->ps.origin, a); + + if (VectorLength(a) <= radius) + { + pl[numpl] = i; + numpl++; + } + } + + i++; + } + + if (numpl < 1) + { + return; + } + + if (numpl == 1) + { + poweradd = 50; + } + else if (numpl == 2) + { + poweradd = 33; + } + else + { + poweradd = 25; + } + self->client->ps.fd.forcePowerDebounce[FP_TEAM_FORCE] = level.time + 2000; + + BG_ForcePowerDrain( &self->client->ps, FP_TEAM_FORCE, forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE] ); + + i = 0; + + while (i < numpl) + { + g_entities[pl[i]].client->ps.fd.forcePower += poweradd; + if (g_entities[pl[i]].client->ps.fd.forcePower > 100) + { + g_entities[pl[i]].client->ps.fd.forcePower = 100; + } + + //At this point we know we got one, so add him into the collective event client bitflag + if (!te) + { + te = G_TempEntity( self->client->ps.origin, EV_TEAM_POWER); + te->s.eventParm = 2; //eventParm 1 is heal, eventParm 2 is force regen + } + + WP_AddToClientBitflags(te, pl[i]); + //Now cramming it all into one event.. doing this many g_sound events at once was a Bad Thing. + + i++; + } +} + +void ForceGrip( gentity_t *self ) +{ + trace_t tr; + vec3_t tfrom, tto, fwd; + + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceHandExtend != HANDEXTEND_NONE) + { + return; + } + + if (self->client->ps.weaponTime > 0) + { + return; + } + + if (self->client->ps.fd.forceGripUseTime > level.time) + { + return; + } + + if ( !WP_ForcePowerUsable( self, FP_GRIP ) ) + { + return; + } + + VectorCopy(self->client->ps.origin, tfrom); + tfrom[2] += self->client->ps.viewheight; + AngleVectors(self->client->ps.viewangles, fwd, NULL, NULL); + tto[0] = tfrom[0] + fwd[0]*MAX_GRIP_DISTANCE; + tto[1] = tfrom[1] + fwd[1]*MAX_GRIP_DISTANCE; + tto[2] = tfrom[2] + fwd[2]*MAX_GRIP_DISTANCE; + + trap_Trace(&tr, tfrom, NULL, NULL, tto, self->s.number, MASK_PLAYERSOLID); + + if ( tr.fraction != 1.0 && + tr.entityNum != ENTITYNUM_NONE && + g_entities[tr.entityNum].client && + !g_entities[tr.entityNum].client->ps.fd.forceGripCripple && + g_entities[tr.entityNum].client->ps.fd.forceGripBeingGripped < level.time && + ForcePowerUsableOn(self, &g_entities[tr.entityNum], FP_GRIP) && + (g_friendlyFire.integer || !OnSameTeam(self, &g_entities[tr.entityNum])) ) //don't grip someone who's still crippled + { + if (g_entities[tr.entityNum].s.number < MAX_CLIENTS && g_entities[tr.entityNum].client->ps.m_iVehicleNum) + { //a player on a vehicle + gentity_t *vehEnt = &g_entities[g_entities[tr.entityNum].client->ps.m_iVehicleNum]; + if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle) + { + if (vehEnt->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || + vehEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) + { //push the guy off + vehEnt->m_pVehicle->m_pVehicleInfo->Eject(vehEnt->m_pVehicle, (bgEntity_t *)&g_entities[tr.entityNum], qfalse); + } + } + } + self->client->ps.fd.forceGripEntityNum = tr.entityNum; + g_entities[tr.entityNum].client->ps.fd.forceGripStarted = level.time; + self->client->ps.fd.forceGripDamageDebounceTime = 0; + + self->client->ps.forceHandExtend = HANDEXTEND_FORCE_HOLD; + self->client->ps.forceHandExtendTime = level.time + 5000; + } + else + { + self->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE; + return; + } +} + +void ForceSpeed( gentity_t *self, int forceDuration ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.fd.forcePowersActive & (1 << FP_SPEED)) ) + { + WP_ForcePowerStop( self, FP_SPEED ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_SPEED ) ) + { + return; + } + + if ( self->client->holdingObjectiveItem >= MAX_CLIENTS + && self->client->holdingObjectiveItem < ENTITYNUM_WORLD ) + {//holding Siege item + if ( g_entities[self->client->holdingObjectiveItem].genericValue15 ) + {//disables force powers + return; + } + } + + self->client->ps.forceAllowDeactivateTime = level.time + 1500; + + WP_ForcePowerStart( self, FP_SPEED, forceDuration ); + G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/speed.wav") ); + G_Sound( self, TRACK_CHANNEL_2, speedLoopSound ); +} + +void ForceSeeing( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.fd.forcePowersActive & (1 << FP_SEE)) ) + { + WP_ForcePowerStop( self, FP_SEE ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_SEE ) ) + { + return; + } + + self->client->ps.forceAllowDeactivateTime = level.time + 1500; + + WP_ForcePowerStart( self, FP_SEE, 0 ); + + G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/see.wav") ); + G_Sound( self, TRACK_CHANNEL_5, seeLoopSound ); +} + +void ForceProtect( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.fd.forcePowersActive & (1 << FP_PROTECT)) ) + { + WP_ForcePowerStop( self, FP_PROTECT ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_PROTECT ) ) + { + return; + } + + // Make sure to turn off Force Rage and Force Absorb. + if (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE) ) + { + WP_ForcePowerStop( self, FP_RAGE ); + } + if (self->client->ps.fd.forcePowersActive & (1 << FP_ABSORB) ) + { + WP_ForcePowerStop( self, FP_ABSORB ); + } + + self->client->ps.forceAllowDeactivateTime = level.time + 1500; + + WP_ForcePowerStart( self, FP_PROTECT, 0 ); + G_PreDefSound(self->client->ps.origin, PDSOUND_PROTECT); + G_Sound( self, TRACK_CHANNEL_3, protectLoopSound ); +} + +void ForceAbsorb( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.fd.forcePowersActive & (1 << FP_ABSORB)) ) + { + WP_ForcePowerStop( self, FP_ABSORB ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_ABSORB ) ) + { + return; + } + + // Make sure to turn off Force Rage and Force Protection. + if (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE) ) + { + WP_ForcePowerStop( self, FP_RAGE ); + } + if (self->client->ps.fd.forcePowersActive & (1 << FP_PROTECT) ) + { + WP_ForcePowerStop( self, FP_PROTECT ); + } + + self->client->ps.forceAllowDeactivateTime = level.time + 1500; + + WP_ForcePowerStart( self, FP_ABSORB, 0 ); + G_PreDefSound(self->client->ps.origin, PDSOUND_ABSORB); + G_Sound( self, TRACK_CHANNEL_3, absorbLoopSound ); +} + +void ForceRage( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE)) ) + { + WP_ForcePowerStop( self, FP_RAGE ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_RAGE ) ) + { + return; + } + + if (self->client->ps.fd.forceRageRecoveryTime >= level.time) + { + return; + } + + if (self->health < 10) + { + return; + } + + // Make sure to turn off Force Protection and Force Absorb. + if (self->client->ps.fd.forcePowersActive & (1 << FP_PROTECT) ) + { + WP_ForcePowerStop( self, FP_PROTECT ); + } + if (self->client->ps.fd.forcePowersActive & (1 << FP_ABSORB) ) + { + WP_ForcePowerStop( self, FP_ABSORB ); + } + + self->client->ps.forceAllowDeactivateTime = level.time + 1500; + + WP_ForcePowerStart( self, FP_RAGE, 0 ); + + G_Sound( self, TRACK_CHANNEL_4, G_SoundIndex("sound/weapons/force/rage.wav") ); + G_Sound( self, TRACK_CHANNEL_3, rageLoopSound ); +} + +void ForceLightning( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + if ( self->client->ps.fd.forcePower < 25 || !WP_ForcePowerUsable( self, FP_LIGHTNING ) ) + { + return; + } + if ( self->client->ps.fd.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.forceHandExtend != HANDEXTEND_NONE) + { + return; + } + + if (self->client->ps.weaponTime > 0) + { + return; + } + + //Shoot lightning from hand + //using grip anim now, to extend the burst time + self->client->ps.forceHandExtend = HANDEXTEND_FORCE_HOLD; + self->client->ps.forceHandExtendTime = level.time + 20000; + + G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/lightning") ); + + WP_ForcePowerStart( self, FP_LIGHTNING, 500 ); +} + +void ForceLightningDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t impactPoint ) +{ + self->client->dangerTime = level.time; + self->client->ps.eFlags &= ~EF_INVULNERABLE; + self->client->invulnerableTimer = 0; + + if ( traceEnt && traceEnt->takedamage ) + { + if (!traceEnt->client && traceEnt->s.eType == ET_NPC) + { //g2animent + if (traceEnt->s.genericenemyindex < level.time) + { + traceEnt->s.genericenemyindex = level.time + 2000; + } + } + if ( traceEnt->client ) + {//an enemy or object + if (traceEnt->client->noLightningTime >= level.time) + { //give them power and don't hurt them. + traceEnt->client->ps.fd.forcePower++; + if (traceEnt->client->ps.fd.forcePower > 100) + { + traceEnt->client->ps.fd.forcePower = 100; + } + return; + } + if (ForcePowerUsableOn(self, traceEnt, FP_LIGHTNING)) + { + int dmg = Q_irand(1,2); //Q_irand( 1, 3 ); + + int modPowerLevel = -1; + + if (traceEnt->client) + { + modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.fd.forcePowerLevel[FP_ABSORB], self, FP_LIGHTNING, self->client->ps.fd.forcePowerLevel[FP_LIGHTNING], 1); + } + + if (modPowerLevel != -1) + { + if (!modPowerLevel) + { + dmg = 0; + traceEnt->client->noLightningTime = level.time + 400; + } + else if (modPowerLevel == 1) + { + dmg = 1; + traceEnt->client->noLightningTime = level.time + 300; + } + else if (modPowerLevel == 2) + { + dmg = 1; + traceEnt->client->noLightningTime = level.time + 100; + } + } + + if ( self->client->ps.weapon == WP_MELEE + && self->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 ) + {//2-handed lightning + //jackin' 'em up, Palpatine-style + dmg *= 2; + } + + if (dmg) + { + //rww - Shields can now absorb lightning too. + G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_DARK ); + } + if ( traceEnt->client ) + { + if ( !Q_irand( 0, 2 ) ) + { + G_Sound( traceEnt, CHAN_BODY, G_SoundIndex( va("sound/weapons/force/lightninghit%i", Q_irand(1, 3) )) ); + } + + if (traceEnt->client->ps.electrifyTime < (level.time + 400)) + { //only update every 400ms to reduce bandwidth usage (as it is passing a 32-bit time value) + traceEnt->client->ps.electrifyTime = level.time + 800; + } + if ( traceEnt->client->ps.powerups[PW_CLOAKED] ) + {//disable cloak temporarily + Jedi_Decloak( traceEnt ); + traceEnt->client->cloakToggleTime = level.time + Q_irand( 3000, 10000 ); + } + } + } + } + } +} + +void ForceShootLightning( gentity_t *self ) +{ + trace_t tr; + vec3_t end, forward; + gentity_t *traceEnt; + + if ( self->health <= 0 ) + { + return; + } + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + + if ( self->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_2 ) + {//arc + vec3_t center, mins, maxs, dir, ent_org, size, v; + float radius = FORCE_LIGHTNING_RADIUS, dot, dist; + gentity_t *entityList[MAX_GENTITIES]; + int iEntityList[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 = trap_EntitiesInBox( mins, maxs, iEntityList, MAX_GENTITIES ); + + i = 0; + while (i < numListedEntities) + { + entityList[i] = &g_entities[iEntityList[i]]; + + i++; + } + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + traceEnt = entityList[e]; + + if ( !traceEnt ) + continue; + if ( traceEnt == self ) + continue; + if ( traceEnt->r.ownerNum == self->s.number && 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; + if ( !g_friendlyFire.integer && OnSameTeam(self, traceEnt)) + 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->r.absmin[i] ) + { + v[i] = traceEnt->r.absmin[i] - center[i]; + } else if ( center[i] > traceEnt->r.absmax[i] ) + { + v[i] = center[i] - traceEnt->r.absmax[i]; + } else + { + v[i] = 0; + } + } + + VectorSubtract( traceEnt->r.absmax, traceEnt->r.absmin, size ); + VectorMA( traceEnt->r.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->r.bmodel && !trap_InPVS( ent_org, self->client->ps.origin ) ) + {//must be in PVS + continue; + } + + //Now check and see if we can actually hit it + trap_Trace( &tr, self->client->ps.origin, 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 + ForceLightningDamage( self, traceEnt, dir, ent_org ); + } + } + else + {//trace-line + VectorMA( self->client->ps.origin, 2048, forward, end ); + + trap_Trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT ); + if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid ) + { + return; + } + + traceEnt = &g_entities[tr.entityNum]; + ForceLightningDamage( self, traceEnt, forward, tr.endpos ); + } +} + +void ForceDrain( gentity_t *self ) +{ + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceHandExtend != HANDEXTEND_NONE) + { + return; + } + + if (self->client->ps.weaponTime > 0) + { + return; + } + + if ( self->client->ps.fd.forcePower < 25 || !WP_ForcePowerUsable( self, FP_DRAIN ) ) + { + return; + } + if ( self->client->ps.fd.forcePowerDebounce[FP_DRAIN] > level.time ) + {//stops it while using it and also after using it, up to 3 second delay + return; + } + +// self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH; +// self->client->ps.forceHandExtendTime = level.time + 1000; + self->client->ps.forceHandExtend = HANDEXTEND_FORCE_HOLD; + self->client->ps.forceHandExtendTime = level.time + 20000; + + G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/drain.wav") ); + + WP_ForcePowerStart( self, FP_DRAIN, 500 ); +} + +void ForceDrainDamage( gentity_t *self, gentity_t *traceEnt, vec3_t dir, vec3_t impactPoint ) +{ + gentity_t *tent; + + self->client->dangerTime = level.time; + self->client->ps.eFlags &= ~EF_INVULNERABLE; + self->client->invulnerableTimer = 0; + + if ( traceEnt && traceEnt->takedamage ) + { + if ( traceEnt->client && (!OnSameTeam(self, traceEnt) || g_friendlyFire.integer) && self->client->ps.fd.forceDrainTime < level.time && traceEnt->client->ps.fd.forcePower ) + {//an enemy or object + if (!traceEnt->client && traceEnt->s.eType == ET_NPC) + { //g2animent + if (traceEnt->s.genericenemyindex < level.time) + { + traceEnt->s.genericenemyindex = level.time + 2000; + } + } + if (ForcePowerUsableOn(self, traceEnt, FP_DRAIN)) + { + int modPowerLevel = -1; + int dmg = 0; //Q_irand( 1, 3 ); + if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_1) + { + dmg = 2; //because it's one-shot + } + else if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_2) + { + dmg = 3; + } + else if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_3) + { + dmg = 4; + } + + if (traceEnt->client) + { + modPowerLevel = WP_AbsorbConversion(traceEnt, traceEnt->client->ps.fd.forcePowerLevel[FP_ABSORB], self, FP_DRAIN, self->client->ps.fd.forcePowerLevel[FP_DRAIN], 1); + } + + if (modPowerLevel != -1) + { + if (!modPowerLevel) + { + dmg = 0; + } + else if (modPowerLevel == 1) + { + dmg = 1; + } + else if (modPowerLevel == 2) + { + dmg = 2; + } + } + //G_Damage( traceEnt, self, self, dir, impactPoint, dmg, 0, MOD_FORCE_DARK ); + + if (dmg) + { + traceEnt->client->ps.fd.forcePower -= (dmg); + } + if (traceEnt->client->ps.fd.forcePower < 0) + { + traceEnt->client->ps.fd.forcePower = 0; + } + + if (self->client->ps.stats[STAT_HEALTH] < self->client->ps.stats[STAT_MAX_HEALTH] && + self->health > 0 && self->client->ps.stats[STAT_HEALTH] > 0) + { + self->health += dmg; + if (self->health > self->client->ps.stats[STAT_MAX_HEALTH]) + { + self->health = self->client->ps.stats[STAT_MAX_HEALTH]; + } + self->client->ps.stats[STAT_HEALTH] = self->health; + } + + traceEnt->client->ps.fd.forcePowerRegenDebounceTime = level.time + 800; //don't let the client being drained get force power back right away + + //Drain the standard amount since we just drained someone else + + /* + if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_1) + { + BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, 0 ); + } + else + { + BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_DRAIN]][FP_DRAIN]/5 ); + } + + if (self->client->ps.fd.forcePowerLevel[FP_DRAIN] == FORCE_LEVEL_1) + { + self->client->ps.fd.forceDrainTime = level.time + 100; + } + else + { + self->client->ps.fd.forceDrainTime = level.time + 20; + } + + if ( traceEnt->client ) + { + if ( !Q_irand( 0, 2 ) ) + { + //G_Sound( traceEnt, CHAN_BODY, G_SoundIndex( "sound/weapons/force/lightninghit.wav" ) ); + } + // traceEnt->s.powerups |= ( 1 << PW_DISINT_1 ); + + // traceEnt->client->ps.powerups[PW_DISINT_1] = level.time + 500; + } + */ + + if (traceEnt->client->forcePowerSoundDebounce < level.time) + { + tent = G_TempEntity( impactPoint, EV_FORCE_DRAINED); + tent->s.eventParm = DirToByte(dir); + tent->s.owner = traceEnt->s.number; + + traceEnt->client->forcePowerSoundDebounce = level.time + 400; + } + } + } + } +} + +int ForceShootDrain( gentity_t *self ) +{ + trace_t tr; + vec3_t end, forward; + gentity_t *traceEnt; + int gotOneOrMore = 0; + + if ( self->health <= 0 ) + { + return 0; + } + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + + if ( self->client->ps.fd.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 iEntityList[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 = trap_EntitiesInBox( mins, maxs, iEntityList, MAX_GENTITIES ); + + i = 0; + while (i < numListedEntities) + { + entityList[i] = &g_entities[iEntityList[i]]; + + i++; + } + + 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.fd.forcePower ) + continue; + if (OnSameTeam(self, traceEnt) && !g_friendlyFire.integer) + 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->r.absmin[i] ) + { + v[i] = traceEnt->r.absmin[i] - center[i]; + } else if ( center[i] > traceEnt->r.absmax[i] ) + { + v[i] = center[i] - traceEnt->r.absmax[i]; + } else + { + v[i] = 0; + } + } + + VectorSubtract( traceEnt->r.absmax, traceEnt->r.absmin, size ); + VectorMA( traceEnt->r.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->r.bmodel && !trap_InPVS( ent_org, self->client->ps.origin ) ) + {//must be in PVS + continue; + } + + //Now check and see if we can actually hit it + trap_Trace( &tr, self->client->ps.origin, 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 + ForceDrainDamage( self, traceEnt, dir, ent_org ); + gotOneOrMore = 1; + } + } + else + {//trace-line + VectorMA( self->client->ps.origin, 2048, forward, end ); + + trap_Trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, end, self->s.number, MASK_SHOT ); + if ( tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid || !g_entities[tr.entityNum].client || !g_entities[tr.entityNum].inuse ) + { + return 0; + } + + traceEnt = &g_entities[tr.entityNum]; + ForceDrainDamage( self, traceEnt, forward, tr.endpos ); + gotOneOrMore = 1; + } + + self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_DRAIN] + FORCE_LEVEL_3; + + BG_ForcePowerDrain( &self->client->ps, FP_DRAIN, 5 ); //used to be 1, but this did, too, anger the God of Balance. + + self->client->ps.fd.forcePowerRegenDebounceTime = level.time + 500; + + return gotOneOrMore; +} + +void ForceJumpCharge( gentity_t *self, usercmd_t *ucmd ) +{ //I guess this is unused now. Was used for the "charge" jump type. + float forceJumpChargeInterval = forceJumpStrength[0] / (FORCE_JUMP_CHARGE_TIME/FRAMETIME); + + if ( self->health <= 0 ) + { + return; + } + + if (!self->client->ps.fd.forceJumpCharge && self->client->ps.groundEntityNum == ENTITYNUM_NONE) + { + return; + } + + if (self->client->ps.fd.forcePower < forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]][FP_LEVITATION]) + { + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE); + return; + } + + if (!self->client->ps.fd.forceJumpCharge) + { + self->client->ps.fd.forceJumpAddTime = 0; + } + + if (self->client->ps.fd.forceJumpAddTime >= level.time) + { + return; + } + + //need to play sound + if ( !self->client->ps.fd.forceJumpCharge ) + { + G_Sound( self, TRACK_CHANNEL_1, G_SoundIndex("sound/weapons/force/jumpbuild.wav") ); + } + + //Increment + if (self->client->ps.fd.forceJumpAddTime < level.time) + { + self->client->ps.fd.forceJumpCharge += forceJumpChargeInterval*50; + self->client->ps.fd.forceJumpAddTime = level.time + 500; + } + + //clamp to max strength for current level + if ( self->client->ps.fd.forceJumpCharge > forceJumpStrength[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]] ) + { + self->client->ps.fd.forceJumpCharge = forceJumpStrength[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]]; + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE); + } + + //clamp to max available force power + if ( self->client->ps.fd.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]][FP_LEVITATION] > self->client->ps.fd.forcePower ) + {//can't use more than you have + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE); + self->client->ps.fd.forceJumpCharge = self->client->ps.fd.forcePower*forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME); + } + + //G_Printf("%f\n", self->client->ps.fd.forceJumpCharge); +} + +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; + } + } + + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE); + + G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEJUMP); + + if (self->client->ps.fd.forceJumpCharge < JUMP_VELOCITY+40) + { //give him at least a tiny boost from just a tap + self->client->ps.fd.forceJumpCharge = JUMP_VELOCITY+400; + } + + if (self->client->ps.velocity[2] < -30) + { //so that we can get a good boost when force jumping in a fall + self->client->ps.velocity[2] = -30; + } + + VectorMA( self->client->ps.velocity, pushFwd, forward, jumpVel ); + VectorMA( self->client->ps.velocity, pushRt, right, jumpVel ); + jumpVel[2] += self->client->ps.fd.forceJumpCharge; + if ( pushFwd > 0 && self->client->ps.fd.forceJumpCharge > 200 ) + { + return FJ_FORWARD; + } + else if ( pushFwd < 0 && self->client->ps.fd.forceJumpCharge > 200 ) + { + return FJ_BACKWARD; + } + else if ( pushRt > 0 && self->client->ps.fd.forceJumpCharge > 200 ) + { + return FJ_RIGHT; + } + else if ( pushRt < 0 && self->client->ps.fd.forceJumpCharge > 200 ) + { + return FJ_LEFT; + } + else + { + return FJ_UP; + } +} + +void ForceJump( gentity_t *self, usercmd_t *ucmd ) +{ + float forceJumpChargeInterval; + vec3_t jumpVel; + + if ( self->client->ps.fd.forcePowerDuration[FP_LEVITATION] > level.time ) + { + return; + } + if ( !WP_ForcePowerUsable( self, FP_LEVITATION ) ) + { + return; + } + if ( self->s.groundEntityNum == ENTITYNUM_NONE ) + { + return; + } + if ( self->health <= 0 ) + { + return; + } + + self->client->fjDidJump = qtrue; + + forceJumpChargeInterval = forceJumpStrength[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]]/(FORCE_JUMP_CHARGE_TIME/FRAMETIME); + + WP_GetVelocityForForceJump( self, jumpVel, ucmd ); + + //FIXME: sound effect + self->client->ps.fd.forceJumpZStart = self->client->ps.origin[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.fd.forceJumpCharge/forceJumpChargeInterval/(FORCE_JUMP_CHARGE_TIME/FRAMETIME)*forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_LEVITATION]][FP_LEVITATION] ); + //self->client->ps.fd.forcePowerDuration[FP_LEVITATION] = level.time + self->client->ps.weaponTime; + self->client->ps.fd.forceJumpCharge = 0; + self->client->ps.forceJumpFlip = qtrue; +} + +void WP_AddAsMindtricked(forcedata_t *fd, int entNum) +{ + if (!fd) + { + return; + } + + if (entNum > 47) + { + fd->forceMindtrickTargetIndex4 |= (1 << (entNum-48)); + } + else if (entNum > 31) + { + fd->forceMindtrickTargetIndex3 |= (1 << (entNum-32)); + } + else if (entNum > 15) + { + fd->forceMindtrickTargetIndex2 |= (1 << (entNum-16)); + } + else + { + fd->forceMindtrickTargetIndex |= (1 << entNum); + } +} + +qboolean ForceTelepathyCheckDirectNPCTarget( gentity_t *self, trace_t *tr, qboolean *tookPower ) +{ + gentity_t *traceEnt; + qboolean targetLive = qfalse, mindTrickDone = qfalse; + vec3_t tfrom, tto, fwd; + float radius = MAX_TRICK_DISTANCE; + + //Check for a direct usage on NPCs first + VectorCopy(self->client->ps.origin, tfrom); + tfrom[2] += self->client->ps.viewheight; + AngleVectors(self->client->ps.viewangles, fwd, NULL, NULL); + tto[0] = tfrom[0] + fwd[0]*radius/2; + tto[1] = tfrom[1] + fwd[1]*radius/2; + tto[2] = tfrom[2] + fwd[2]*radius/2; + + trap_Trace( tr, tfrom, NULL, NULL, tto, self->s.number, MASK_PLAYERSOLID ); + + if ( tr->entityNum == ENTITYNUM_NONE + || tr->fraction == 1.0f + || tr->allsolid + || tr->startsolid ) + { + return qfalse; + } + + traceEnt = &g_entities[tr->entityNum]; + + if( traceEnt->NPC + && traceEnt->NPC->scriptFlags & SCF_NO_FORCE ) + { + return qfalse; + } + + 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_BOBAFETT: + case CLASS_RANCOR: + break; + default: + targetLive = qtrue; + break; + } + } + + if ( traceEnt->s.number < MAX_CLIENTS ) + {//a regular client + return qfalse; + } + + if ( targetLive && traceEnt->NPC ) + {//hit an organic non-player + vec3_t eyeDir; + 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 ( (self->NPC && traceEnt->client->playerTeam != self->client->playerTeam) + || (!self->NPC && traceEnt->client->playerTeam != self->client->sess.sessionTeam) ) + {//an enemy + int override = 0; + if ( (traceEnt->NPC->scriptFlags&SCF_NO_MIND_TRICK) ) + { + } + else if ( traceEnt->s.weapon != WP_SABER + && traceEnt->client->NPC_class != CLASS_REBORN ) + {//haha! Jedi aren't easily confused! + if ( self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_2 ) + {//turn them to our side + //if mind trick 3 and aiming at an enemy need more force power + if ( traceEnt->s.weapon != WP_NONE ) + {//don't charm people who aren't capable of fighting... like ugnaughts and droids + int newPlayerTeam, newEnemyTeam; + + 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? + if ( self->NPC ) + {//NPC + newPlayerTeam = self->client->playerTeam; + newEnemyTeam = self->client->enemyTeam; + } + else + {//client/bot + if ( self->client->sess.sessionTeam == TEAM_BLUE ) + {//rebel + newPlayerTeam = NPCTEAM_PLAYER; + newEnemyTeam = NPCTEAM_ENEMY; + } + else if ( self->client->sess.sessionTeam == TEAM_RED ) + {//imperial + newPlayerTeam = NPCTEAM_ENEMY; + newEnemyTeam = NPCTEAM_PLAYER; + } + else + {//neutral - wan't attack anyone + newPlayerTeam = NPCTEAM_NEUTRAL; + newEnemyTeam = NPCTEAM_NEUTRAL; + } + } + //store these for retrieval later + traceEnt->genericValue1 = traceEnt->client->playerTeam; + traceEnt->genericValue2 = traceEnt->client->enemyTeam; + traceEnt->genericValue3 = traceEnt->s.teamowner; + //set the new values + traceEnt->client->playerTeam = newPlayerTeam; + traceEnt->client->enemyTeam = newEnemyTeam; + traceEnt->s.teamowner = newPlayerTeam; + //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.fd.forcePowerLevel[FP_TELEPATHY]]; + } + } + 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.fd.forcePowerLevel[FP_TELEPATHY]];//confused for about 10 seconds + 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? + AngleVectors( traceEnt->client->renderInfo.eyeAngles, eyeDir, NULL, NULL ); + VectorNormalize( eyeDir ); + G_PlayEffectID( G_EffectIndex( "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 + mindTrickDone = qtrue; + } + else + { + if ( self->client->ps.fd.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_PlayEffectID( 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 ); + *tookPower = qtrue; + } + //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.fd.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * g_timescale->value ); + } + */ + return qtrue; +} + +void ForceTelepathy(gentity_t *self) +{ + trace_t tr; + vec3_t tto, thispush_org, a; + vec3_t mins, maxs, fwdangles, forward, right, center; + int i; + float visionArc = 0; + float radius = MAX_TRICK_DISTANCE; + qboolean tookPower = qfalse; + + if ( self->health <= 0 ) + { + return; + } + + if (self->client->ps.forceHandExtend != HANDEXTEND_NONE) + { + return; + } + + if (self->client->ps.weaponTime > 0) + { + return; + } + + if (self->client->ps.powerups[PW_REDFLAG] || + self->client->ps.powerups[PW_BLUEFLAG]) + { //can't mindtrick while carrying the flag + return; + } + + if (self->client->ps.forceAllowDeactivateTime < level.time && + (self->client->ps.fd.forcePowersActive & (1 << FP_TELEPATHY)) ) + { + WP_ForcePowerStop( self, FP_TELEPATHY ); + return; + } + + if ( !WP_ForcePowerUsable( self, FP_TELEPATHY ) ) + { + return; + } + + if ( ForceTelepathyCheckDirectNPCTarget( self, &tr, &tookPower ) ) + {//hit an NPC directly + self->client->ps.forceAllowDeactivateTime = level.time + 1500; + G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/distract.wav") ); + self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH; + self->client->ps.forceHandExtendTime = level.time + 1000; + return; + } + + if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_2) + { + visionArc = 180; + } + else if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_3) + { + visionArc = 360; + radius = MAX_TRICK_DISTANCE*2.0f; + } + + VectorCopy( self->client->ps.viewangles, fwdangles ); + AngleVectors( fwdangles, forward, right, NULL ); + VectorCopy( self->client->ps.origin, center ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + + if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_1) + { + if (tr.fraction != 1.0 && + tr.entityNum != ENTITYNUM_NONE && + g_entities[tr.entityNum].inuse && + g_entities[tr.entityNum].client && + g_entities[tr.entityNum].client->pers.connected && + g_entities[tr.entityNum].client->sess.sessionTeam != TEAM_SPECTATOR) + { + WP_AddAsMindtricked(&self->client->ps.fd, tr.entityNum); + if ( !tookPower ) + { + WP_ForcePowerStart( self, FP_TELEPATHY, 0 ); + } + + G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/distract.wav") ); + + self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH; + self->client->ps.forceHandExtendTime = level.time + 1000; + + return; + } + else + { + return; + } + } + else //level 2 & 3 + { + gentity_t *ent; + int entityList[MAX_GENTITIES]; + int numListedEntities; + int e = 0; + qboolean gotatleastone = qfalse; + + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + while (e < numListedEntities) + { + ent = &g_entities[entityList[e]]; + + if (ent) + { //not in the arc, don't consider it + if (ent->client) + { + VectorCopy(ent->client->ps.origin, thispush_org); + } + else + { + VectorCopy(ent->s.pos.trBase, thispush_org); + } + VectorCopy(self->client->ps.origin, tto); + tto[2] += self->client->ps.viewheight; + VectorSubtract(thispush_org, tto, a); + vectoangles(a, a); + + if (!ent->client) + { + entityList[e] = ENTITYNUM_NONE; + } + else if (!InFieldOfVision(self->client->ps.viewangles, visionArc, a)) + { //only bother with arc rules if the victim is a client + entityList[e] = ENTITYNUM_NONE; + } + else if (!ForcePowerUsableOn(self, ent, FP_TELEPATHY)) + { + entityList[e] = ENTITYNUM_NONE; + } + else if (OnSameTeam(self, ent)) + { + entityList[e] = ENTITYNUM_NONE; + } + } + ent = &g_entities[entityList[e]]; + if (ent && ent != self && ent->client) + { + gotatleastone = qtrue; + WP_AddAsMindtricked(&self->client->ps.fd, ent->s.number); + } + e++; + } + + if (gotatleastone) + { + self->client->ps.forceAllowDeactivateTime = level.time + 1500; + + if ( !tookPower ) + { + WP_ForcePowerStart( self, FP_TELEPATHY, 0 ); + } + + G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/distract.wav") ); + + self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH; + self->client->ps.forceHandExtendTime = level.time + 1000; + } + } + +} + +void GEntity_UseFunc( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + GlobalUse(self, other, activator); +} + +qboolean CanCounterThrow(gentity_t *self, gentity_t *thrower, qboolean pull) +{ + int powerUse = 0; + + if (self->client->ps.forceHandExtend != HANDEXTEND_NONE) + { + return 0; + } + + if (self->client->ps.weaponTime > 0) + { + return 0; + } + + if ( self->health <= 0 ) + { + return 0; + } + + if ( self->client->ps.powerups[PW_DISINT_4] > level.time ) + { + return 0; + } + + if (self->client->ps.weaponstate == WEAPON_CHARGING || + self->client->ps.weaponstate == WEAPON_CHARGING_ALT) + { //don't autodefend when charging a weapon + return 0; + } + + if (g_gametype.integer == GT_SIEGE && + pull && + thrower && thrower->client) + { //in siege, pull will affect people if they are not facing you, so they can't run away so much + vec3_t d; + float a; + + VectorSubtract(thrower->client->ps.origin, self->client->ps.origin, d); + vectoangles(d, d); + + a = AngleSubtract(d[YAW], self->client->ps.viewangles[YAW]); + + if (a > 60.0f || a < -60.0f) + { //if facing more than 60 degrees away they cannot defend + return 0; + } + } + + if (pull) + { + powerUse = FP_PULL; + } + else + { + powerUse = FP_PUSH; + } + + if ( !WP_ForcePowerUsable( self, powerUse ) ) + { + return 0; + } + + if (self->client->ps.groundEntityNum == ENTITYNUM_NONE) + { //you cannot counter a push/pull if you're in the air + return 0; + } + + return 1; +} + +qboolean G_InGetUpAnim(playerState_t *ps) +{ + switch( (ps->legsAnim) ) + { + 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; + } + + switch( (ps->torsoAnim) ) + { + 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; + } + + return qfalse; +} + +void G_LetGoOfWall( gentity_t *ent ) +{ + if ( !ent || !ent->client ) + { + return; + } + ent->client->ps.pm_flags &= ~PMF_STUCK_TO_WALL; + if ( BG_InReboundJump( ent->client->ps.legsAnim ) + || BG_InReboundHold( ent->client->ps.legsAnim ) ) + { + ent->client->ps.legsTimer = 0; + } + if ( BG_InReboundJump( ent->client->ps.torsoAnim ) + || BG_InReboundHold( ent->client->ps.torsoAnim ) ) + { + ent->client->ps.torsoTimer = 0; + } +} + +float forcePushPullRadius[NUM_FORCE_POWER_LEVELS] = +{ + 0,//none + 384,//256, + 448,//384, + 512 +}; +//rwwFIXMEFIXME: incorporate this into the below function? Currently it's only being used by jedi AI + +extern void Touch_Button(gentity_t *ent, gentity_t *other, trace_t *trace ); +void ForceThrow( gentity_t *self, qboolean pull ) +{ + //shove things in front of you away + float dist; + gentity_t *ent; + int entityList[MAX_GENTITIES]; + gentity_t *push_list[MAX_GENTITIES]; + int numListedEntities; + vec3_t mins, maxs; + vec3_t v; + int i, e; + int ent_count = 0; + int radius = 1024; //since it's view-based now. //350; + int powerLevel; + int visionArc; + int pushPower; + int pushPowerMod; + vec3_t center, ent_org, size, forward, right, end, dir, fwdangles = {0}; + float dot1; + trace_t tr; + int x; + vec3_t pushDir; + vec3_t thispush_org; + vec3_t tfrom, tto, fwd, a; + float knockback = pull?0:200; + int powerUse = 0; + + visionArc = 0; + + if (self->client->ps.forceHandExtend != HANDEXTEND_NONE && (self->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN || !G_InGetUpAnim(&self->client->ps))) + { + return; + } + + if (!g_useWhileThrowing.integer && self->client->ps.saberInFlight) + { + return; + } + + if (self->client->ps.weaponTime > 0) + { + return; + } + + if ( self->health <= 0 ) + { + return; + } + if ( self->client->ps.powerups[PW_DISINT_4] > level.time ) + { + return; + } + if (pull) + { + powerUse = FP_PULL; + } + else + { + powerUse = FP_PUSH; + } + + if ( !WP_ForcePowerUsable( self, powerUse ) ) + { + return; + } + + if (!pull && self->client->ps.saberLockTime > level.time && self->client->ps.saberLockFrame) + { + G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/weapons/force/push.wav" ) ); + self->client->ps.powerups[PW_DISINT_4] = level.time + 1500; + + self->client->ps.saberLockHits += self->client->ps.fd.forcePowerLevel[FP_PUSH]*2; + + WP_ForcePowerStart( self, FP_PUSH, 0 ); + return; + } + + WP_ForcePowerStart( self, powerUse, 0 ); + + //make sure this plays and that you cannot press fire for about 1 second after this + if ( pull ) + { + G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/weapons/force/pull.wav" ) ); + if (self->client->ps.forceHandExtend == HANDEXTEND_NONE) + { + self->client->ps.forceHandExtend = HANDEXTEND_FORCEPULL; + if ( g_gametype.integer == GT_SIEGE && self->client->ps.weapon == WP_SABER ) + {//hold less so can attack right after a pull + self->client->ps.forceHandExtendTime = level.time + 200; + } + else + { + self->client->ps.forceHandExtendTime = level.time + 400; + } + } + self->client->ps.powerups[PW_DISINT_4] = self->client->ps.forceHandExtendTime + 200; + self->client->ps.powerups[PW_PULL] = self->client->ps.powerups[PW_DISINT_4]; + } + else + { + G_Sound( self, CHAN_BODY, G_SoundIndex( "sound/weapons/force/push.wav" ) ); + if (self->client->ps.forceHandExtend == HANDEXTEND_NONE) + { + self->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH; + self->client->ps.forceHandExtendTime = level.time + 1000; + } + else if (self->client->ps.forceHandExtend == HANDEXTEND_KNOCKDOWN && G_InGetUpAnim(&self->client->ps)) + { + if (self->client->ps.forceDodgeAnim > 4) + { + self->client->ps.forceDodgeAnim -= 8; + } + self->client->ps.forceDodgeAnim += 8; //special case, play push on upper torso, but keep playing current knockdown anim on legs + } + self->client->ps.powerups[PW_DISINT_4] = level.time + 1100; + self->client->ps.powerups[PW_PULL] = 0; + } + + VectorCopy( self->client->ps.viewangles, fwdangles ); + AngleVectors( fwdangles, forward, right, NULL ); + VectorCopy( self->client->ps.origin, center ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = center[i] - radius; + maxs[i] = center[i] + radius; + } + + + if (pull) + { + powerLevel = self->client->ps.fd.forcePowerLevel[FP_PULL]; + pushPower = 256*self->client->ps.fd.forcePowerLevel[FP_PULL]; + } + else + { + powerLevel = self->client->ps.fd.forcePowerLevel[FP_PUSH]; + pushPower = 256*self->client->ps.fd.forcePowerLevel[FP_PUSH]; + } + + if (!powerLevel) + { //Shouldn't have made it here.. + return; + } + + if (powerLevel == FORCE_LEVEL_2) + { + visionArc = 60; + } + else if (powerLevel == FORCE_LEVEL_3) + { + visionArc = 180; + } + + if (powerLevel == FORCE_LEVEL_1) + { //can only push/pull targeted things at level 1 + VectorCopy(self->client->ps.origin, tfrom); + tfrom[2] += self->client->ps.viewheight; + AngleVectors(self->client->ps.viewangles, fwd, NULL, NULL); + tto[0] = tfrom[0] + fwd[0]*radius/2; + tto[1] = tfrom[1] + fwd[1]*radius/2; + tto[2] = tfrom[2] + fwd[2]*radius/2; + + trap_Trace(&tr, tfrom, NULL, NULL, tto, self->s.number, MASK_PLAYERSOLID); + + if (tr.fraction != 1.0 && + tr.entityNum != ENTITYNUM_NONE) + { + if (!g_entities[tr.entityNum].client && g_entities[tr.entityNum].s.eType == ET_NPC) + { //g2animent + if (g_entities[tr.entityNum].s.genericenemyindex < level.time) + { + g_entities[tr.entityNum].s.genericenemyindex = level.time + 2000; + } + } + + numListedEntities = 0; + entityList[numListedEntities] = tr.entityNum; + + if (pull) + { + if (!ForcePowerUsableOn(self, &g_entities[tr.entityNum], FP_PULL)) + { + return; + } + } + else + { + if (!ForcePowerUsableOn(self, &g_entities[tr.entityNum], FP_PUSH)) + { + return; + } + } + numListedEntities++; + } + else + { + //didn't get anything, so just + return; + } + } + else + { + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + e = 0; + + while (e < numListedEntities) + { + ent = &g_entities[entityList[e]]; + + if (!ent->client && ent->s.eType == ET_NPC) + { //g2animent + if (ent->s.genericenemyindex < level.time) + { + ent->s.genericenemyindex = level.time + 2000; + } + } + + if (ent) + { + if (ent->client) + { + VectorCopy(ent->client->ps.origin, thispush_org); + } + else + { + VectorCopy(ent->s.pos.trBase, thispush_org); + } + } + + if (ent) + { //not in the arc, don't consider it + VectorCopy(self->client->ps.origin, tto); + tto[2] += self->client->ps.viewheight; + VectorSubtract(thispush_org, tto, a); + vectoangles(a, a); + + if (ent->client && !InFieldOfVision(self->client->ps.viewangles, visionArc, a) && + ForcePowerUsableOn(self, ent, powerUse)) + { //only bother with arc rules if the victim is a client + entityList[e] = ENTITYNUM_NONE; + } + else if (ent->client) + { + if (pull) + { + if (!ForcePowerUsableOn(self, ent, FP_PULL)) + { + entityList[e] = ENTITYNUM_NONE; + } + } + else + { + if (!ForcePowerUsableOn(self, ent, FP_PUSH)) + { + entityList[e] = ENTITYNUM_NONE; + } + } + } + } + e++; + } + } + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + if (entityList[e] != ENTITYNUM_NONE && + entityList[e] >= 0 && + entityList[e] < MAX_GENTITIES) + { + ent = &g_entities[entityList[e]]; + } + else + { + ent = NULL; + } + + if (!ent) + continue; + if (ent == self) + continue; + if (ent->client && OnSameTeam(ent, self)) + { + continue; + } + if ( !(ent->inuse) ) + continue; + if ( ent->s.eType != ET_MISSILE ) + { + if ( ent->s.eType != ET_ITEM ) + { + //FIXME: need pushable objects + if ( Q_stricmp( "func_button", ent->classname ) == 0 ) + {//we might push it + if ( pull || !(ent->spawnflags&SPF_BUTTON_FPUSHABLE) ) + {//not force-pushable, never pullable + continue; + } + } + else + { + if ( ent->s.eFlags & EF_NODRAW ) + { + continue; + } + if ( !ent->client ) + { + if ( Q_stricmp( "lightsaber", ent->classname ) != 0 ) + {//not a lightsaber + 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*/)) ) + {//not a force-usable func_static + if ( Q_stricmp( "limb", ent->classname ) ) + {//not a limb + continue; + } + } + } + else if ( ent->moverState != MOVER_POS1 && ent->moverState != MOVER_POS2 ) + {//not at rest + continue; + } + } + } + else if ( ent->client->NPC_class == CLASS_GALAKMECH + || ent->client->NPC_class == CLASS_ATST + || ent->client->NPC_class == CLASS_RANCOR ) + {//can't push ATST or Galak or Rancor + continue; + } + } + } + } + else + { + if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) ) + {//can't force-push/pull stuck missiles (detpacks, tripmines) + continue; + } + if ( ent->s.pos.trType == TR_STATIONARY && ent->s.weapon != WP_THERMAL ) + {//only thermal detonators can be pushed once stopped + 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->r.absmin[i] ) + { + v[i] = ent->r.absmin[i] - center[i]; + } else if ( center[i] > ent->r.absmax[i] ) + { + v[i] = center[i] - ent->r.absmax[i]; + } else + { + v[i] = 0; + } + } + + VectorSubtract( ent->r.absmax, ent->r.absmin, size ); + VectorMA( ent->r.absmin, 0.5, size, ent_org ); + + VectorSubtract( ent_org, center, dir ); + VectorNormalize( dir ); + if ( (dot1 = DotProduct( dir, forward )) < 0.6 ) + continue; + + dist = VectorLength( v ); + + //Now check and see if we can actually deflect it + //method1 + //if within a certain range, deflect it + if ( dist >= radius ) + { + continue; + } + + //in PVS? + if ( !ent->r.bmodel && !trap_InPVS( ent_org, self->client->ps.origin ) ) + {//must be in PVS + continue; + } + + //really should have a clear LOS to this thing... + trap_Trace( &tr, self->client->ps.origin, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT ); + if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number ) + {//must have clear LOS + //try from eyes too before you give up + vec3_t eyePoint; + VectorCopy(self->client->ps.origin, eyePoint); + eyePoint[2] += self->client->ps.viewheight; + trap_Trace( &tr, eyePoint, vec3_origin, vec3_origin, ent_org, self->s.number, MASK_SHOT ); + + if ( tr.fraction < 1.0f && tr.entityNum != ent->s.number ) + { + continue; + } + } + + // ok, we are within the radius, add us to the incoming list + push_list[ent_count] = ent; + ent_count++; + } + + if ( ent_count ) + { + //method1: + for ( x = 0; x < ent_count; x++ ) + { + int modPowerLevel = powerLevel; + + + if (push_list[x]->client) + { + modPowerLevel = WP_AbsorbConversion(push_list[x], push_list[x]->client->ps.fd.forcePowerLevel[FP_ABSORB], self, powerUse, powerLevel, forcePowerNeeded[self->client->ps.fd.forcePowerLevel[powerUse]][powerUse]); + if (modPowerLevel == -1) + { + modPowerLevel = powerLevel; + } + } + + pushPower = 256*modPowerLevel; + + if (push_list[x]->client) + { + VectorCopy(push_list[x]->client->ps.origin, thispush_org); + } + else + { + VectorCopy(push_list[x]->s.origin, thispush_org); + } + + if ( push_list[x]->client ) + {//FIXME: make enemy jedi able to hunker down and resist this? + int otherPushPower = push_list[x]->client->ps.fd.forcePowerLevel[powerUse]; + qboolean canPullWeapon = qtrue; + float dirLen = 0; + + if ( g_debugMelee.integer ) + { + if ( (push_list[x]->client->ps.pm_flags&PMF_STUCK_TO_WALL) ) + {//no resistance if stuck to wall + //push/pull them off the wall + otherPushPower = 0; + G_LetGoOfWall( push_list[x] ); + } + } + + knockback = pull?0:200; + + pushPowerMod = pushPower; + + if (push_list[x]->client->pers.cmd.forwardmove || + push_list[x]->client->pers.cmd.rightmove) + { //if you are moving, you get one less level of defense + otherPushPower--; + + if (otherPushPower < 0) + { + otherPushPower = 0; + } + } + + if (otherPushPower && CanCounterThrow(push_list[x], self, pull)) + { + if ( pull ) + { + G_Sound( push_list[x], CHAN_BODY, G_SoundIndex( "sound/weapons/force/pull.wav" ) ); + push_list[x]->client->ps.forceHandExtend = HANDEXTEND_FORCEPULL; + push_list[x]->client->ps.forceHandExtendTime = level.time + 400; + } + else + { + G_Sound( push_list[x], CHAN_BODY, G_SoundIndex( "sound/weapons/force/push.wav" ) ); + push_list[x]->client->ps.forceHandExtend = HANDEXTEND_FORCEPUSH; + push_list[x]->client->ps.forceHandExtendTime = level.time + 1000; + } + push_list[x]->client->ps.powerups[PW_DISINT_4] = push_list[x]->client->ps.forceHandExtendTime + 200; + + if (pull) + { + push_list[x]->client->ps.powerups[PW_PULL] = push_list[x]->client->ps.powerups[PW_DISINT_4]; + } + else + { + push_list[x]->client->ps.powerups[PW_PULL] = 0; + } + + //Make a counter-throw effect + + if (otherPushPower >= modPowerLevel) + { + pushPowerMod = 0; + canPullWeapon = qfalse; + } + else + { + int powerDif = (modPowerLevel - otherPushPower); + + if (powerDif >= 3) + { + pushPowerMod -= pushPowerMod*0.2; + } + else if (powerDif == 2) + { + pushPowerMod -= pushPowerMod*0.4; + } + else if (powerDif == 1) + { + pushPowerMod -= pushPowerMod*0.8; + } + + if (pushPowerMod < 0) + { + pushPowerMod = 0; + } + } + } + + //shove them + if ( pull ) + { + VectorSubtract( self->client->ps.origin, thispush_org, pushDir ); + + if (push_list[x]->client && VectorLength(pushDir) <= 256) + { + int randfact = 0; + + if (modPowerLevel == FORCE_LEVEL_1) + { + randfact = 3; + } + else if (modPowerLevel == FORCE_LEVEL_2) + { + randfact = 7; + } + else if (modPowerLevel == FORCE_LEVEL_3) + { + randfact = 10; + } + + if (!OnSameTeam(self, push_list[x]) && Q_irand(1, 10) <= randfact && canPullWeapon) + { + vec3_t uorg, vecnorm; + + VectorCopy(self->client->ps.origin, uorg); + uorg[2] += 64; + + VectorSubtract(uorg, thispush_org, vecnorm); + VectorNormalize(vecnorm); + + TossClientWeapon(push_list[x], vecnorm, 500); + } + } + } + else + { + VectorSubtract( thispush_org, self->client->ps.origin, pushDir ); + } + + if ((modPowerLevel > otherPushPower || push_list[x]->client->ps.m_iVehicleNum) && push_list[x]->client) + { + if (modPowerLevel == FORCE_LEVEL_3 && + push_list[x]->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN) + { + dirLen = VectorLength(pushDir); + + if (BG_KnockDownable(&push_list[x]->client->ps) && + dirLen <= (64*((modPowerLevel - otherPushPower)-1))) + { //can only do a knockdown if fairly close + push_list[x]->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + push_list[x]->client->ps.forceHandExtendTime = level.time + 700; + push_list[x]->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + push_list[x]->client->ps.quickerGetup = qtrue; + } + else if (push_list[x]->s.number < MAX_CLIENTS && push_list[x]->client->ps.m_iVehicleNum && + dirLen <= 128.0f ) + { //a player on a vehicle + gentity_t *vehEnt = &g_entities[push_list[x]->client->ps.m_iVehicleNum]; + if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle) + { + if (vehEnt->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || + vehEnt->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) + { //push the guy off + vehEnt->m_pVehicle->m_pVehicleInfo->Eject(vehEnt->m_pVehicle, (bgEntity_t *)push_list[x], qfalse); + } + } + } + } + } + + if (!dirLen) + { + dirLen = VectorLength(pushDir); + } + + VectorNormalize(pushDir); + + if (push_list[x]->client) + { + //escape a force grip if we're in one + if (self->client->ps.fd.forceGripBeingGripped > level.time) + { //force the enemy to stop gripping me if I managed to push him + if (push_list[x]->client->ps.fd.forceGripEntityNum == self->s.number) + { + if (modPowerLevel >= push_list[x]->client->ps.fd.forcePowerLevel[FP_GRIP]) + { //only break the grip if our push/pull level is >= their grip level + WP_ForcePowerStop(push_list[x], FP_GRIP); + self->client->ps.fd.forceGripBeingGripped = 0; + push_list[x]->client->ps.fd.forceGripUseTime = level.time + 1000; //since we just broke out of it.. + } + } + } + + push_list[x]->client->ps.otherKiller = self->s.number; + push_list[x]->client->ps.otherKillerTime = level.time + 5000; + push_list[x]->client->ps.otherKillerDebounceTime = level.time + 100; + + pushPowerMod -= (dirLen*0.7); + if (pushPowerMod < 16) + { + pushPowerMod = 16; + } + + //fullbody push effect + push_list[x]->client->pushEffectTime = level.time + 600; + + push_list[x]->client->ps.velocity[0] = pushDir[0]*pushPowerMod; + push_list[x]->client->ps.velocity[1] = pushDir[1]*pushPowerMod; + + if ((int)push_list[x]->client->ps.velocity[2] == 0) + { //if not going anywhere vertically, boost them up a bit + push_list[x]->client->ps.velocity[2] = pushDir[2]*pushPowerMod; + + if (push_list[x]->client->ps.velocity[2] < 128) + { + push_list[x]->client->ps.velocity[2] = 128; + } + } + else + { + push_list[x]->client->ps.velocity[2] = pushDir[2]*pushPowerMod; + } + } + } + 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 + { + if ( pull ) + {//deflect rather than reflect? + } + else + { + G_ReflectMissile( self, push_list[x], forward ); + } + } + else if ( !Q_stricmp( "func_static", push_list[x]->classname ) ) + {//force-usable func_static + if ( !pull && (push_list[x]->spawnflags&1/*F_PUSH*/) ) + { + GEntity_UseFunc( push_list[x], self, self ); + } + else if ( pull && (push_list[x]->spawnflags&2/*F_PULL*/) ) + { + GEntity_UseFunc( push_list[x], self, self ); + } + } + else if ( !Q_stricmp( "func_door", push_list[x]->classname ) && (push_list[x]->spawnflags&2) ) + {//push/pull the door + vec3_t pos1, pos2; + vec3_t trFrom; + + VectorCopy(self->client->ps.origin, trFrom); + trFrom[2] += self->client->ps.viewheight; + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + VectorNormalize( forward ); + VectorMA( trFrom, radius, forward, end ); + trap_Trace( &tr, trFrom, 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]->r.absmax, push_list[x]->r.absmin, size ); + VectorMA( push_list[x]->r.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]->r.currentOrigin, center ); + VectorCopy( push_list[x]->pos1, pos1 ); + VectorCopy( push_list[x]->pos2, pos2 ); + } + + if ( Distance( pos1, trFrom ) < Distance( pos2, trFrom ) ) + {//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 ( Q_stricmp( "func_button", push_list[x]->classname ) == 0 ) + {//pretend you pushed it + Touch_Button( push_list[x], self, NULL ); + continue; + } + } + } + + //attempt to break any leftover grips + //if we're still in a current grip that wasn't broken by the push, it will still remain + self->client->dangerTime = level.time; + self->client->ps.eFlags &= ~EF_INVULNERABLE; + self->client->invulnerableTimer = 0; + + if (self->client->ps.fd.forceGripBeingGripped > level.time) + { + self->client->ps.fd.forceGripBeingGripped = 0; + } +} + +void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower ) +{ + int wasActive = self->client->ps.fd.forcePowersActive; + + self->client->ps.fd.forcePowersActive &= ~( 1 << forcePower ); + + switch( (int)forcePower ) + { + case FP_HEAL: + self->client->ps.fd.forceHealAmount = 0; + self->client->ps.fd.forceHealTime = 0; + break; + case FP_LEVITATION: + break; + case FP_SPEED: + if (wasActive & (1 << FP_SPEED)) + { + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_2-50], CHAN_VOICE); + } + break; + case FP_PUSH: + break; + case FP_PULL: + break; + case FP_TELEPATHY: + if (wasActive & (1 << FP_TELEPATHY)) + { + G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/weapons/force/distractstop.wav") ); + } + self->client->ps.fd.forceMindtrickTargetIndex = 0; + self->client->ps.fd.forceMindtrickTargetIndex2 = 0; + self->client->ps.fd.forceMindtrickTargetIndex3 = 0; + self->client->ps.fd.forceMindtrickTargetIndex4 = 0; + break; + case FP_SEE: + if (wasActive & (1 << FP_SEE)) + { + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_5-50], CHAN_VOICE); + } + break; + case FP_GRIP: + self->client->ps.fd.forceGripUseTime = level.time + 3000; + if (self->client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 && + g_entities[self->client->ps.fd.forceGripEntityNum].client && + g_entities[self->client->ps.fd.forceGripEntityNum].health > 0 && + g_entities[self->client->ps.fd.forceGripEntityNum].inuse && + (level.time - g_entities[self->client->ps.fd.forceGripEntityNum].client->ps.fd.forceGripStarted) > 500) + { //if we had our throat crushed in for more than half a second, gasp for air when we're let go + if (wasActive & (1 << FP_GRIP)) + { + G_EntitySound( &g_entities[self->client->ps.fd.forceGripEntityNum], CHAN_VOICE, G_SoundIndex("*gasp.wav") ); + } + } + + if (g_entities[self->client->ps.fd.forceGripEntityNum].client && + g_entities[self->client->ps.fd.forceGripEntityNum].inuse) + { + + g_entities[self->client->ps.fd.forceGripEntityNum].client->ps.forceGripChangeMovetype = PM_NORMAL; + } + + if (self->client->ps.forceHandExtend == HANDEXTEND_FORCE_HOLD) + { + self->client->ps.forceHandExtendTime = 0; + } + + self->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE; + + self->client->ps.powerups[PW_DISINT_4] = 0; + break; + case FP_LIGHTNING: + if ( self->client->ps.fd.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.fd.forcePowerDebounce[FP_LIGHTNING] = level.time + 3000; + } + else + { + self->client->ps.fd.forcePowerDebounce[FP_LIGHTNING] = level.time + 1500; + } + if (self->client->ps.forceHandExtend == HANDEXTEND_FORCE_HOLD) + { + self->client->ps.forceHandExtendTime = 0; //reset hand position + } + + self->client->ps.activeForcePass = 0; + break; + case FP_RAGE: + self->client->ps.fd.forceRageRecoveryTime = level.time + 10000; + if (wasActive & (1 << FP_RAGE)) + { + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_3-50], CHAN_VOICE); + } + break; + case FP_ABSORB: + if (wasActive & (1 << FP_ABSORB)) + { + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_3-50], CHAN_VOICE); + } + break; + case FP_PROTECT: + if (wasActive & (1 << FP_PROTECT)) + { + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_3-50], CHAN_VOICE); + } + break; + case FP_DRAIN: + if ( self->client->ps.fd.forcePowerLevel[FP_DRAIN] < FORCE_LEVEL_2 ) + {//don't do it again for 3 seconds, minimum... + self->client->ps.fd.forcePowerDebounce[FP_DRAIN] = level.time + 3000; + } + else + { + self->client->ps.fd.forcePowerDebounce[FP_DRAIN] = level.time + 1500; + } + + if (self->client->ps.forceHandExtend == HANDEXTEND_FORCE_HOLD) + { + self->client->ps.forceHandExtendTime = 0; //reset hand position + } + + self->client->ps.activeForcePass = 0; + default: + break; + } +} + +void DoGripAction(gentity_t *self, forcePowers_t forcePower) +{ + gentity_t *gripEnt; + int gripLevel = 0; + trace_t tr; + vec3_t a; + vec3_t fwd, fwd_o, start_o, nvel; + + self->client->dangerTime = level.time; + self->client->ps.eFlags &= ~EF_INVULNERABLE; + self->client->invulnerableTimer = 0; + + gripEnt = &g_entities[self->client->ps.fd.forceGripEntityNum]; + + if (!gripEnt || !gripEnt->client || !gripEnt->inuse || gripEnt->health < 1 || !ForcePowerUsableOn(self, gripEnt, FP_GRIP)) + { + WP_ForcePowerStop(self, forcePower); + self->client->ps.fd.forceGripEntityNum = ENTITYNUM_NONE; + + if (gripEnt && gripEnt->client && gripEnt->inuse) + { + gripEnt->client->ps.forceGripChangeMovetype = PM_NORMAL; + } + return; + } + + VectorSubtract(gripEnt->client->ps.origin, self->client->ps.origin, a); + + trap_Trace(&tr, self->client->ps.origin, NULL, NULL, gripEnt->client->ps.origin, self->s.number, MASK_PLAYERSOLID); + + gripLevel = WP_AbsorbConversion(gripEnt, gripEnt->client->ps.fd.forcePowerLevel[FP_ABSORB], self, FP_GRIP, self->client->ps.fd.forcePowerLevel[FP_GRIP], forcePowerNeeded[self->client->ps.fd.forcePowerLevel[FP_GRIP]][FP_GRIP]); + + if (gripLevel == -1) + { + gripLevel = self->client->ps.fd.forcePowerLevel[FP_GRIP]; + } + + if (!gripLevel) + { + WP_ForcePowerStop(self, forcePower); + return; + } + + if (VectorLength(a) > MAX_GRIP_DISTANCE) + { + WP_ForcePowerStop(self, forcePower); + return; + } + + if ( !InFront( gripEnt->client->ps.origin, self->client->ps.origin, self->client->ps.viewangles, 0.9f ) && + gripLevel < FORCE_LEVEL_3) + { + WP_ForcePowerStop(self, forcePower); + return; + } + + if (tr.fraction != 1.0f && + tr.entityNum != gripEnt->s.number /*&& + gripLevel < FORCE_LEVEL_3*/) + { + WP_ForcePowerStop(self, forcePower); + return; + } + + if (self->client->ps.fd.forcePowerDebounce[FP_GRIP] < level.time) + { //2 damage per second while choking, resulting in 10 damage total (not including The Squeeze) + self->client->ps.fd.forcePowerDebounce[FP_GRIP] = level.time + 1000; + G_Damage(gripEnt, self, self, NULL, NULL, 2, DAMAGE_NO_ARMOR, MOD_FORCE_DARK); + } + + Jetpack_Off(gripEnt); //make sure the guy being gripped has his jetpack off. + + if (gripLevel == FORCE_LEVEL_1) + { + gripEnt->client->ps.fd.forceGripBeingGripped = level.time + 1000; + + if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 5000) + { + WP_ForcePowerStop(self, forcePower); + } + return; + } + + if (gripLevel == FORCE_LEVEL_2) + { + gripEnt->client->ps.fd.forceGripBeingGripped = level.time + 1000; + + if (gripEnt->client->ps.forceGripMoveInterval < level.time) + { + gripEnt->client->ps.velocity[2] = 30; + + gripEnt->client->ps.forceGripMoveInterval = level.time + 300; //only update velocity every 300ms, so as to avoid heavy bandwidth usage + } + + gripEnt->client->ps.otherKiller = self->s.number; + gripEnt->client->ps.otherKillerTime = level.time + 5000; + gripEnt->client->ps.otherKillerDebounceTime = level.time + 100; + + gripEnt->client->ps.forceGripChangeMovetype = PM_FLOAT; + + if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 3000 && !self->client->ps.fd.forceGripDamageDebounceTime) + { //if we managed to lift him into the air for 2 seconds, give him a crack + self->client->ps.fd.forceGripDamageDebounceTime = 1; + G_Damage(gripEnt, self, self, NULL, NULL, 20, DAMAGE_NO_ARMOR, MOD_FORCE_DARK); + + //Must play custom sounds on the actual entity. Don't use G_Sound (it creates a temp entity for the sound) + G_EntitySound( gripEnt, CHAN_VOICE, G_SoundIndex(va( "*choke%d.wav", Q_irand( 1, 3 ) )) ); + + gripEnt->client->ps.forceHandExtend = HANDEXTEND_CHOKE; + gripEnt->client->ps.forceHandExtendTime = level.time + 2000; + + if (gripEnt->client->ps.fd.forcePowersActive & (1 << FP_GRIP)) + { //choking, so don't let him keep gripping himself + WP_ForcePowerStop(gripEnt, FP_GRIP); + } + } + else if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 4000) + { + WP_ForcePowerStop(self, forcePower); + } + return; + } + + if (gripLevel == FORCE_LEVEL_3) + { + gripEnt->client->ps.fd.forceGripBeingGripped = level.time + 1000; + + gripEnt->client->ps.otherKiller = self->s.number; + gripEnt->client->ps.otherKillerTime = level.time + 5000; + gripEnt->client->ps.otherKillerDebounceTime = level.time + 100; + + gripEnt->client->ps.forceGripChangeMovetype = PM_FLOAT; + + if (gripEnt->client->ps.forceGripMoveInterval < level.time) + { + float nvLen = 0; + + VectorCopy(gripEnt->client->ps.origin, start_o); + AngleVectors(self->client->ps.viewangles, fwd, NULL, NULL); + fwd_o[0] = self->client->ps.origin[0] + fwd[0]*128; + fwd_o[1] = self->client->ps.origin[1] + fwd[1]*128; + fwd_o[2] = self->client->ps.origin[2] + fwd[2]*128; + fwd_o[2] += 16; + VectorSubtract(fwd_o, start_o, nvel); + + nvLen = VectorLength(nvel); + + if (nvLen < 16) + { //within x units of desired spot + VectorNormalize(nvel); + gripEnt->client->ps.velocity[0] = nvel[0]*8; + gripEnt->client->ps.velocity[1] = nvel[1]*8; + gripEnt->client->ps.velocity[2] = nvel[2]*8; + } + else if (nvLen < 64) + { + VectorNormalize(nvel); + gripEnt->client->ps.velocity[0] = nvel[0]*128; + gripEnt->client->ps.velocity[1] = nvel[1]*128; + gripEnt->client->ps.velocity[2] = nvel[2]*128; + } + else if (nvLen < 128) + { + VectorNormalize(nvel); + gripEnt->client->ps.velocity[0] = nvel[0]*256; + gripEnt->client->ps.velocity[1] = nvel[1]*256; + gripEnt->client->ps.velocity[2] = nvel[2]*256; + } + else if (nvLen < 200) + { + VectorNormalize(nvel); + gripEnt->client->ps.velocity[0] = nvel[0]*512; + gripEnt->client->ps.velocity[1] = nvel[1]*512; + gripEnt->client->ps.velocity[2] = nvel[2]*512; + } + else + { + VectorNormalize(nvel); + gripEnt->client->ps.velocity[0] = nvel[0]*700; + gripEnt->client->ps.velocity[1] = nvel[1]*700; + gripEnt->client->ps.velocity[2] = nvel[2]*700; + } + + gripEnt->client->ps.forceGripMoveInterval = level.time + 300; //only update velocity every 300ms, so as to avoid heavy bandwidth usage + } + + if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 3000 && !self->client->ps.fd.forceGripDamageDebounceTime) + { //if we managed to lift him into the air for 2 seconds, give him a crack + self->client->ps.fd.forceGripDamageDebounceTime = 1; + G_Damage(gripEnt, self, self, NULL, NULL, 40, DAMAGE_NO_ARMOR, MOD_FORCE_DARK); + + //Must play custom sounds on the actual entity. Don't use G_Sound (it creates a temp entity for the sound) + G_EntitySound( gripEnt, CHAN_VOICE, G_SoundIndex(va( "*choke%d.wav", Q_irand( 1, 3 ) )) ); + + gripEnt->client->ps.forceHandExtend = HANDEXTEND_CHOKE; + gripEnt->client->ps.forceHandExtendTime = level.time + 2000; + + if (gripEnt->client->ps.fd.forcePowersActive & (1 << FP_GRIP)) + { //choking, so don't let him keep gripping himself + WP_ForcePowerStop(gripEnt, FP_GRIP); + } + } + else if ((level.time - gripEnt->client->ps.fd.forceGripStarted) > 4000) + { + WP_ForcePowerStop(self, forcePower); + } + return; + } +} + +qboolean G_IsMindTricked(forcedata_t *fd, int client) +{ + int checkIn; + int trickIndex1, trickIndex2, trickIndex3, trickIndex4; + int sub = 0; + + if (!fd) + { + return qfalse; + } + + trickIndex1 = fd->forceMindtrickTargetIndex; + trickIndex2 = fd->forceMindtrickTargetIndex2; + trickIndex3 = fd->forceMindtrickTargetIndex3; + trickIndex4 = fd->forceMindtrickTargetIndex4; + + 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 qtrue; + } + + return qfalse; +} + +static void RemoveTrickedEnt(forcedata_t *fd, int client) +{ + if (!fd) + { + return; + } + + if (client > 47) + { + fd->forceMindtrickTargetIndex4 &= ~(1 << (client-48)); + } + else if (client > 31) + { + fd->forceMindtrickTargetIndex3 &= ~(1 << (client-32)); + } + else if (client > 15) + { + fd->forceMindtrickTargetIndex2 &= ~(1 << (client-16)); + } + else + { + fd->forceMindtrickTargetIndex &= ~(1 << client); + } +} + +extern int g_LastFrameTime; +extern int g_TimeSinceLastFrame; + +static void WP_UpdateMindtrickEnts(gentity_t *self) +{ + int i = 0; + + while (i < MAX_CLIENTS) + { + if (G_IsMindTricked(&self->client->ps.fd, i)) + { + gentity_t *ent = &g_entities[i]; + + if ( !ent || !ent->client || !ent->inuse || ent->health < 1 || + (ent->client->ps.fd.forcePowersActive & (1 << FP_SEE)) ) + { + RemoveTrickedEnt(&self->client->ps.fd, i); + } + else if ((level.time - self->client->dangerTime) < g_TimeSinceLastFrame*4) + { //Untrick this entity if the tricker (self) fires while in his fov + if (trap_InPVS(ent->client->ps.origin, self->client->ps.origin) && + OrgVisible(ent->client->ps.origin, self->client->ps.origin, ent->s.number)) + { + RemoveTrickedEnt(&self->client->ps.fd, i); + } + } + else if (BG_HasYsalamiri(g_gametype.integer, &ent->client->ps)) + { + RemoveTrickedEnt(&self->client->ps.fd, i); + } + } + + i++; + } + + if (!self->client->ps.fd.forceMindtrickTargetIndex && + !self->client->ps.fd.forceMindtrickTargetIndex2 && + !self->client->ps.fd.forceMindtrickTargetIndex3 && + !self->client->ps.fd.forceMindtrickTargetIndex4) + { //everyone who we had tricked is no longer tricked, so stop the power + WP_ForcePowerStop(self, FP_TELEPATHY); + } + else if (self->client->ps.powerups[PW_REDFLAG] || + self->client->ps.powerups[PW_BLUEFLAG]) + { + WP_ForcePowerStop(self, FP_TELEPATHY); + } +} + +static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd ) +{ + extern usercmd_t ucmd; + + switch( (int)forcePower ) + { + case FP_HEAL: + if (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_1) + { + if (self->client->ps.velocity[0] || self->client->ps.velocity[1] || self->client->ps.velocity[2]) + { + WP_ForcePowerStop( self, forcePower ); + break; + } + } + + if (self->health < 1 || self->client->ps.stats[STAT_HEALTH] < 1) + { + WP_ForcePowerStop( self, forcePower ); + break; + } + + if (self->client->ps.fd.forceHealTime > level.time) + { + break; + } + if ( self->health > self->client->ps.stats[STAT_MAX_HEALTH]) + { //rww - we might start out over max_health and we don't want force heal taking us down to 100 or whatever max_health is + WP_ForcePowerStop( self, forcePower ); + break; + } + self->client->ps.fd.forceHealTime = level.time + 1000; + self->health++; + self->client->ps.fd.forceHealAmount++; + + if ( self->health > self->client->ps.stats[STAT_MAX_HEALTH]) // Past max health + { + self->health = self->client->ps.stats[STAT_MAX_HEALTH]; + WP_ForcePowerStop( self, forcePower ); + } + + if ( (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_1 && self->client->ps.fd.forceHealAmount >= 25) || + (self->client->ps.fd.forcePowerLevel[FP_HEAL] == FORCE_LEVEL_2 && self->client->ps.fd.forceHealAmount >= 33)) + { + WP_ForcePowerStop( self, forcePower ); + } + break; + case FP_SPEED: + //This is handled in PM_WalkMove and PM_StepSlideMove + if ( self->client->holdingObjectiveItem >= MAX_CLIENTS + && self->client->holdingObjectiveItem < ENTITYNUM_WORLD ) + { + if ( g_entities[self->client->holdingObjectiveItem].genericValue15 ) + {//disables force powers + WP_ForcePowerStop( self, forcePower ); + } + } + /* + if ( self->client->ps.powerups[PW_REDFLAG] + || self->client->ps.powerups[PW_BLUEFLAG] + || self->client->ps.powerups[PW_NEUTRALFLAG] ) + {//no force speed when carrying flag + WP_ForcePowerStop( self, forcePower ); + } + */ + break; + case FP_GRIP: + if (self->client->ps.forceHandExtend != HANDEXTEND_FORCE_HOLD) + { + WP_ForcePowerStop(self, FP_GRIP); + break; + } + + if (self->client->ps.fd.forcePowerDebounce[FP_PULL] < level.time) + { //This is sort of not ideal. Using the debounce value reserved for pull for this because pull doesn't need it. + BG_ForcePowerDrain( &self->client->ps, forcePower, 1 ); + self->client->ps.fd.forcePowerDebounce[FP_PULL] = level.time + 100; + } + + if (self->client->ps.fd.forcePower < 1) + { + WP_ForcePowerStop(self, FP_GRIP); + break; + } + + DoGripAction(self, forcePower); + break; + case FP_LEVITATION: + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.fd.forceJumpZStart ) + {//done with jump + WP_ForcePowerStop( self, forcePower ); + } + break; + 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.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1) + { + addTime = 150; + } + else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2) + { + addTime = 300; + } + else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3) + { + addTime = 450; + } + self->client->ps.forceRageDrainTime = level.time + addTime; + } + + if (self->health < 1) + { + self->health = 1; + WP_ForcePowerStop(self, forcePower); + } + + self->client->ps.stats[STAT_HEALTH] = self->health; + break; + case FP_DRAIN: + if (self->client->ps.forceHandExtend != HANDEXTEND_FORCE_HOLD) + { + WP_ForcePowerStop(self, forcePower); + break; + } + + if ( self->client->ps.fd.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 ) + {//higher than level 1 + if ( (cmd->buttons & BUTTON_FORCE_DRAIN) || ((cmd->buttons & BUTTON_FORCEPOWER) && self->client->ps.fd.forcePowerSelected == FP_DRAIN) ) + {//holding it keeps it going + self->client->ps.fd.forcePowerDuration[FP_DRAIN] = level.time + 500; + } + } + // OVERRIDEFIXME + if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) || self->client->ps.fd.forcePowerDuration[FP_DRAIN] < level.time || + self->client->ps.fd.forcePower < 25) + { + WP_ForcePowerStop( self, forcePower ); + } + else + { + ForceShootDrain( self ); + } + break; + case FP_LIGHTNING: + if (self->client->ps.forceHandExtend != HANDEXTEND_FORCE_HOLD) + { //Animation for hand extend doesn't end with hand out, so we have to limit lightning intervals by animation intervals (once hand starts to go in in animation, lightning should stop) + WP_ForcePowerStop(self, forcePower); + break; + } + + if ( self->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 ) + {//higher than level 1 + if ( (cmd->buttons & BUTTON_FORCE_LIGHTNING) || ((cmd->buttons & BUTTON_FORCEPOWER) && self->client->ps.fd.forcePowerSelected == FP_LIGHTNING) ) + {//holding it keeps it going + self->client->ps.fd.forcePowerDuration[FP_LIGHTNING] = level.time + 500; + } + } + // OVERRIDEFIXME + if ( !WP_ForcePowerAvailable( self, forcePower, 0 ) || self->client->ps.fd.forcePowerDuration[FP_LIGHTNING] < level.time || + self->client->ps.fd.forcePower < 25) + { + WP_ForcePowerStop( self, forcePower ); + } + else + { + ForceShootLightning( self ); + BG_ForcePowerDrain( &self->client->ps, forcePower, 0 ); + } + break; + case FP_TELEPATHY: + if ( self->client->holdingObjectiveItem >= MAX_CLIENTS + && self->client->holdingObjectiveItem < ENTITYNUM_WORLD + && g_entities[self->client->holdingObjectiveItem].genericValue15 ) + { //if force hindered can't mindtrick whilst carrying a siege item + WP_ForcePowerStop( self, FP_TELEPATHY ); + } + else + { + WP_UpdateMindtrickEnts(self); + } + break; + case FP_SABER_OFFENSE: + break; + case FP_SABER_DEFENSE: + break; + case FP_SABERTHROW: + break; + case FP_PROTECT: + if (self->client->ps.fd.forcePowerDebounce[forcePower] < level.time) + { + BG_ForcePowerDrain( &self->client->ps, forcePower, 1 ); + if (self->client->ps.fd.forcePower < 1) + { + WP_ForcePowerStop(self, forcePower); + } + + self->client->ps.fd.forcePowerDebounce[forcePower] = level.time + 300; + } + break; + case FP_ABSORB: + if (self->client->ps.fd.forcePowerDebounce[forcePower] < level.time) + { + BG_ForcePowerDrain( &self->client->ps, forcePower, 1 ); + if (self->client->ps.fd.forcePower < 1) + { + WP_ForcePowerStop(self, forcePower); + } + + self->client->ps.fd.forcePowerDebounce[forcePower] = level.time + 600; + } + break; + default: + break; + } +} + +int WP_DoSpecificPower( gentity_t *self, usercmd_t *ucmd, forcePowers_t forcepower) +{ + int powerSucceeded; + + powerSucceeded = 1; + + // OVERRIDEFIXME + if ( !WP_ForcePowerAvailable( self, forcepower, 0 ) ) + { + return 0; + } + + switch(forcepower) + { + case FP_HEAL: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceHeal(self); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_LEVITATION: + //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 ) + { + self->client->ps.fd.forceJumpCharge = 0; + G_MuteSound( self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE ); + //This only happens if the groundEntityNum == ENTITYNUM_NONE when the button is actually released + } + else + {//still on ground, so jump + ForceJump( self, ucmd ); + } + break; + case FP_SPEED: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceSpeed(self, 0); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_GRIP: + if (self->client->ps.fd.forceGripEntityNum == ENTITYNUM_NONE) + { + ForceGrip( self ); + } + + if (self->client->ps.fd.forceGripEntityNum != ENTITYNUM_NONE) + { + if (!(self->client->ps.fd.forcePowersActive & (1 << FP_GRIP))) + { + WP_ForcePowerStart( self, FP_GRIP, 0 ); + BG_ForcePowerDrain( &self->client->ps, FP_GRIP, GRIP_DRAIN_AMOUNT ); + } + } + else + { + powerSucceeded = 0; + } + break; + case FP_LIGHTNING: + ForceLightning(self); + break; + case FP_PUSH: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease && !(self->r.svFlags & SVF_BOT)) + { //need to release before we can use nonhold powers again + break; + } + ForceThrow(self, qfalse); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_PULL: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceThrow(self, qtrue); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_TELEPATHY: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceTelepathy(self); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_RAGE: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceRage(self); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_PROTECT: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceProtect(self); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_ABSORB: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceAbsorb(self); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_TEAM_HEAL: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceTeamHeal(self); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_TEAM_FORCE: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceTeamForceReplenish(self); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_DRAIN: + ForceDrain(self); + break; + case FP_SEE: + powerSucceeded = 0; //always 0 for nonhold powers + if (self->client->ps.fd.forceButtonNeedRelease) + { //need to release before we can use nonhold powers again + break; + } + ForceSeeing(self); + self->client->ps.fd.forceButtonNeedRelease = 1; + break; + case FP_SABER_OFFENSE: + break; + case FP_SABER_DEFENSE: + break; + case FP_SABERTHROW: + break; + default: + break; + } + + return powerSucceeded; +} + +void FindGenericEnemyIndex(gentity_t *self) +{ //Find another client that would be considered a threat. + int i = 0; + float tlen; + gentity_t *ent; + gentity_t *besten = NULL; + float blen = 99999999; + vec3_t a; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->s.number != self->s.number && ent->health > 0 && !OnSameTeam(self, ent) && ent->client->ps.pm_type != PM_INTERMISSION && ent->client->ps.pm_type != PM_SPECTATOR) + { + VectorSubtract(ent->client->ps.origin, self->client->ps.origin, a); + tlen = VectorLength(a); + + if (tlen < blen && + InFront(ent->client->ps.origin, self->client->ps.origin, self->client->ps.viewangles, 0.8f ) && + OrgVisible(self->client->ps.origin, ent->client->ps.origin, self->s.number)) + { + blen = tlen; + besten = ent; + } + } + + i++; + } + + if (!besten) + { + return; + } + + self->client->ps.genericEnemyIndex = besten->s.number; +} + +void SeekerDroneUpdate(gentity_t *self) +{ + vec3_t org, elevated, dir, a, endir; + gentity_t *en; + float angle; + float prefig = 0; + trace_t tr; + + if (!(self->client->ps.eFlags & EF_SEEKERDRONE)) + { + self->client->ps.genericEnemyIndex = -1; + return; + } + + if (self->health < 1) + { + VectorCopy(self->client->ps.origin, elevated); + elevated[2] += 40; + + angle = ((level.time / 12) & 255) * (M_PI * 2) / 255; //magical numbers make magic happen + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 5; + VectorAdd(elevated, dir, org); + + a[ROLL] = 0; + a[YAW] = 0; + a[PITCH] = 1; + + G_PlayEffect(EFFECT_SPARK_EXPLOSION, org, a); + + self->client->ps.eFlags -= EF_SEEKERDRONE; + self->client->ps.genericEnemyIndex = -1; + + return; + } + + if (self->client->ps.droneExistTime >= level.time && + self->client->ps.droneExistTime < (level.time+5000)) + { + self->client->ps.genericEnemyIndex = 1024+self->client->ps.droneExistTime; + if (self->client->ps.droneFireTime < level.time) + { + G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/laser_trap/warning.wav") ); + self->client->ps.droneFireTime = level.time + 100; + } + return; + } + else if (self->client->ps.droneExistTime < level.time) + { + VectorCopy(self->client->ps.origin, elevated); + elevated[2] += 40; + + prefig = (self->client->ps.droneExistTime-level.time)/80; + + if (prefig > 55) + { + prefig = 55; + } + else if (prefig < 1) + { + prefig = 1; + } + + elevated[2] -= 55-prefig; + + angle = ((level.time / 12) & 255) * (M_PI * 2) / 255; //magical numbers make magic happen + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 5; + VectorAdd(elevated, dir, org); + + a[ROLL] = 0; + a[YAW] = 0; + a[PITCH] = 1; + + G_PlayEffect(EFFECT_SPARK_EXPLOSION, org, a); + + self->client->ps.eFlags -= EF_SEEKERDRONE; + self->client->ps.genericEnemyIndex = -1; + + return; + } + + if (self->client->ps.genericEnemyIndex == -1) + { + self->client->ps.genericEnemyIndex = ENTITYNUM_NONE; + } + + if (self->client->ps.genericEnemyIndex != ENTITYNUM_NONE && self->client->ps.genericEnemyIndex != -1) + { + en = &g_entities[self->client->ps.genericEnemyIndex]; + + if (!en || !en->client) + { + self->client->ps.genericEnemyIndex = ENTITYNUM_NONE; + } + else if (en->s.number == self->s.number) + { + self->client->ps.genericEnemyIndex = ENTITYNUM_NONE; + } + else if (en->health < 1) + { + self->client->ps.genericEnemyIndex = ENTITYNUM_NONE; + } + else if (OnSameTeam(self, en)) + { + self->client->ps.genericEnemyIndex = ENTITYNUM_NONE; + } + else + { + if (!InFront(en->client->ps.origin, self->client->ps.origin, self->client->ps.viewangles, 0.8f )) + { + self->client->ps.genericEnemyIndex = ENTITYNUM_NONE; + } + else if (!OrgVisible(self->client->ps.origin, en->client->ps.origin, self->s.number)) + { + self->client->ps.genericEnemyIndex = ENTITYNUM_NONE; + } + } + } + + if (self->client->ps.genericEnemyIndex == ENTITYNUM_NONE || self->client->ps.genericEnemyIndex == -1) + { + FindGenericEnemyIndex(self); + } + + if (self->client->ps.genericEnemyIndex != ENTITYNUM_NONE && self->client->ps.genericEnemyIndex != -1) + { + en = &g_entities[self->client->ps.genericEnemyIndex]; + + VectorCopy(self->client->ps.origin, elevated); + elevated[2] += 40; + + angle = ((level.time / 12) & 255) * (M_PI * 2) / 255; //magical numbers make magic happen + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 5; + VectorAdd(elevated, dir, org); + + //org is now where the thing should be client-side because it uses the same time-based offset + if (self->client->ps.droneFireTime < level.time) + { + trap_Trace(&tr, org, NULL, NULL, en->client->ps.origin, -1, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + VectorSubtract(en->client->ps.origin, org, endir); + VectorNormalize(endir); + + WP_FireGenericBlasterMissile(self, org, endir, 0, 15, 2000, MOD_BLASTER); + G_SoundAtLoc( org, CHAN_WEAPON, G_SoundIndex("sound/weapons/bryar/fire.wav") ); + + self->client->ps.droneFireTime = level.time + Q_irand(400, 700); + } + } + } +} + +void HolocronUpdate(gentity_t *self) +{ //keep holocron status updated in holocron mode + int i = 0; + int noHRank = 0; + + if (noHRank < FORCE_LEVEL_0) + { + noHRank = FORCE_LEVEL_0; + } + if (noHRank > FORCE_LEVEL_3) + { + noHRank = FORCE_LEVEL_3; + } + + trap_Cvar_Update(&g_MaxHolocronCarry); + + while (i < NUM_FORCE_POWERS) + { + if (self->client->ps.holocronsCarried[i]) + { //carrying it, make sure we have the power + self->client->ps.holocronBits |= (1 << i); + self->client->ps.fd.forcePowersKnown |= (1 << i); + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3; + } + else + { //otherwise, make sure the power is cleared from us + self->client->ps.fd.forcePowerLevel[i] = 0; + if (self->client->ps.holocronBits & (1 << i)) + { + self->client->ps.holocronBits -= (1 << i); + } + + if ((self->client->ps.fd.forcePowersKnown & (1 << i)) && i != FP_LEVITATION && i != FP_SABER_OFFENSE) + { + self->client->ps.fd.forcePowersKnown -= (1 << i); + } + + if ((self->client->ps.fd.forcePowersActive & (1 << i)) && i != FP_LEVITATION && i != FP_SABER_OFFENSE) + { + WP_ForcePowerStop(self, i); + } + + if (i == FP_LEVITATION) + { + if (noHRank >= FORCE_LEVEL_1) + { + self->client->ps.fd.forcePowerLevel[i] = noHRank; + } + else + { + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_1; + } + } + else if (i == FP_SABER_OFFENSE) + { + self->client->ps.fd.forcePowersKnown |= (1 << i); + + if (noHRank >= FORCE_LEVEL_1) + { + self->client->ps.fd.forcePowerLevel[i] = noHRank; + } + else + { + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_1; + } + } + else + { + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_0; + } + } + + i++; + } + + if (HasSetSaberOnly()) + { //if saberonly, we get these powers no matter what (still need the holocrons for level 3) + if (self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] < FORCE_LEVEL_1) + { + self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1; + } + if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] < FORCE_LEVEL_1) + { + self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1; + } + } +} + +void JediMasterUpdate(gentity_t *self) +{ //keep jedi master status updated for JM gametype + int i = 0; + + trap_Cvar_Update(&g_MaxHolocronCarry); + + while (i < NUM_FORCE_POWERS) + { + if (self->client->ps.isJediMaster) + { + self->client->ps.fd.forcePowersKnown |= (1 << i); + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3; + + if (i == FP_TEAM_HEAL || i == FP_TEAM_FORCE || + i == FP_DRAIN || i == FP_ABSORB) + { //team powers are useless in JM, absorb is too because no one else has powers to absorb. Drain is just + //relatively useless in comparison, because its main intent is not to heal, but rather to cripple others + //by draining their force at the same time. And no one needs force in JM except the JM himself. + self->client->ps.fd.forcePowersKnown &= ~(1 << i); + self->client->ps.fd.forcePowerLevel[i] = 0; + } + + if (i == FP_TELEPATHY) + { //this decision was made because level 3 mindtrick allows the JM to just hide too much, and no one else has force + //sight to counteract it. Since the JM himself is the focus of gameplay in this mode, having him hidden for large + //durations is indeed a bad thing. + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_2; + } + } + else + { + if ((self->client->ps.fd.forcePowersKnown & (1 << i)) && i != FP_LEVITATION) + { + self->client->ps.fd.forcePowersKnown -= (1 << i); + } + + if ((self->client->ps.fd.forcePowersActive & (1 << i)) && i != FP_LEVITATION) + { + WP_ForcePowerStop(self, i); + } + + if (i == FP_LEVITATION) + { + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_1; + } + else + { + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_0; + } + } + + i++; + } +} + +qboolean WP_HasForcePowers( const playerState_t *ps ) +{ + int i; + if ( ps ) + { + for ( i = 0; i < NUM_FORCE_POWERS; i++ ) + { + if ( i == FP_LEVITATION ) + { + if ( ps->fd.forcePowerLevel[i] > FORCE_LEVEL_1 ) + { + return qtrue; + } + } + else if ( ps->fd.forcePowerLevel[i] > FORCE_LEVEL_0 ) + { + return qtrue; + } + } + } + return qfalse; +} + +//try a special roll getup move +qboolean G_SpecialRollGetup(gentity_t *self) +{ //fixme: currently no knockdown will actually land you on your front... so froll's are pretty useless at the moment. + qboolean rolled = qfalse; + + /* + if (self->client->ps.weapon != WP_SABER && + self->client->ps.weapon != WP_MELEE) + { //can't do acrobatics without saber selected + return qfalse; + } + */ + + if (/*!self->client->pers.cmd.upmove &&*/ + self->client->pers.cmd.rightmove > 0 && + !self->client->pers.cmd.forwardmove) + { + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP_BROLL_R, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 0); + rolled = qtrue; + } + else if (/*!self->client->pers.cmd.upmove &&*/ + self->client->pers.cmd.rightmove < 0 && + !self->client->pers.cmd.forwardmove) + { + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP_BROLL_L, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 0); + rolled = qtrue; + } + else if (/*self->client->pers.cmd.upmove > 0 &&*/ + !self->client->pers.cmd.rightmove && + self->client->pers.cmd.forwardmove > 0) + { + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP_BROLL_F, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 0); + rolled = qtrue; + } + else if (/*self->client->pers.cmd.upmove > 0 &&*/ + !self->client->pers.cmd.rightmove && + self->client->pers.cmd.forwardmove < 0) + { + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP_BROLL_B, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD, 0); + rolled = qtrue; + } + else if (self->client->pers.cmd.upmove) + { + G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEJUMP); + self->client->ps.forceDodgeAnim = 2; + self->client->ps.forceHandExtendTime = level.time + 500; + + //self->client->ps.velocity[2] = 300; + } + + if (rolled) + { + G_EntitySound( self, CHAN_VOICE, G_SoundIndex("*jump1.wav") ); + } + + return rolled; +} + +void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd ) +{ + qboolean usingForce = qfalse; + int i, holo, holoregen; + int prepower = 0; + //see if any force powers are running + if ( !self ) + { + return; + } + + if ( !self->client ) + { + return; + } + + if (self->client->ps.pm_flags & PMF_FOLLOW) + { //not a "real" game client, it's a spectator following someone + return; + } + if (self->client->sess.sessionTeam == TEAM_SPECTATOR) + { + return; + } + + /* + if (self->client->ps.fd.saberAnimLevel > self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { + self->client->ps.fd.saberAnimLevel = self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]; + } + else if (!self->client->ps.fd.saberAnimLevel) + { + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1; + } + */ + //The stance in relation to power level is no longer applicable with the crazy new akimbo/staff stances. + if (!self->client->ps.fd.saberAnimLevel) + { + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1; + } + + if (g_gametype.integer != GT_SIEGE) + { + if (!(self->client->ps.fd.forcePowersKnown & (1 << FP_LEVITATION))) + { + self->client->ps.fd.forcePowersKnown |= (1 << FP_LEVITATION); + } + + if (self->client->ps.fd.forcePowerLevel[FP_LEVITATION] < FORCE_LEVEL_1) + { + self->client->ps.fd.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_1; + } + } + + if (self->client->ps.fd.forcePowerSelected < 0) + { //bad + self->client->ps.fd.forcePowerSelected = 0; + } + + if ( ((self->client->sess.selectedFP != self->client->ps.fd.forcePowerSelected) || + (self->client->sess.saberLevel != self->client->ps.fd.saberAnimLevel)) && + !(self->r.svFlags & SVF_BOT) ) + { + if (self->client->sess.updateUITime < level.time) + { //a bit hackish, but we don't want the client to flood with userinfo updates if they rapidly cycle + //through their force powers or saber attack levels + + self->client->sess.selectedFP = self->client->ps.fd.forcePowerSelected; + self->client->sess.saberLevel = self->client->ps.fd.saberAnimLevel; + } + } + + if (!g_LastFrameTime) + { + g_LastFrameTime = level.time; + } + + if (self->client->ps.forceHandExtend == HANDEXTEND_KNOCKDOWN) + { + self->client->ps.zoomFov = 0; + self->client->ps.zoomMode = 0; + self->client->ps.zoomLocked = qfalse; + self->client->ps.zoomTime = 0; + } + + if (self->client->ps.forceHandExtend == HANDEXTEND_KNOCKDOWN && + self->client->ps.forceHandExtendTime >= level.time) + { + self->client->ps.saberMove = 0; + self->client->ps.saberBlocking = 0; + self->client->ps.saberBlocked = 0; + self->client->ps.weaponTime = 0; + self->client->ps.weaponstate = WEAPON_READY; + } + else if (self->client->ps.forceHandExtend != HANDEXTEND_NONE && + self->client->ps.forceHandExtendTime < level.time) + { + if (self->client->ps.forceHandExtend == HANDEXTEND_KNOCKDOWN && + !self->client->ps.forceDodgeAnim) + { + if (self->health < 1 || (self->client->ps.eFlags & EF_DEAD)) + { + self->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + else if (G_SpecialRollGetup(self)) + { + self->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + else + { //hmm.. ok.. no more getting up on your own, you've gotta push something, unless.. + if ((level.time-self->client->ps.forceHandExtendTime) > 4000) + { //4 seconds elapsed, I guess they're too dumb to push something to get up! + if (self->client->pers.cmd.upmove && + self->client->ps.fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1) + { //force getup + G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEJUMP); + self->client->ps.forceDodgeAnim = 2; + self->client->ps.forceHandExtendTime = level.time + 500; + + //self->client->ps.velocity[2] = 400; + } + else if (self->client->ps.quickerGetup) + { + G_EntitySound( self, CHAN_VOICE, G_SoundIndex("*jump1.wav") ); + self->client->ps.forceDodgeAnim = 3; + self->client->ps.forceHandExtendTime = level.time + 500; + self->client->ps.velocity[2] = 300; + } + else + { + self->client->ps.forceDodgeAnim = 1; + self->client->ps.forceHandExtendTime = level.time + 1000; + } + } + } + self->client->ps.quickerGetup = qfalse; + } + else if (self->client->ps.forceHandExtend == HANDEXTEND_POSTTHROWN) + { + if (self->health < 1 || (self->client->ps.eFlags & EF_DEAD)) + { + self->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + else if (self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceDodgeAnim) + { + self->client->ps.forceDodgeAnim = 1; + self->client->ps.forceHandExtendTime = level.time + 1000; + G_EntitySound( self, CHAN_VOICE, G_SoundIndex("*jump1.wav") ); + self->client->ps.velocity[2] = 100; + } + else if (!self->client->ps.forceDodgeAnim) + { + self->client->ps.forceHandExtendTime = level.time + 100; + } + else + { + self->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY; + } + } + else + { + self->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY; + } + } + + if (g_gametype.integer == GT_HOLOCRON) + { + HolocronUpdate(self); + } + if (g_gametype.integer == GT_JEDIMASTER) + { + JediMasterUpdate(self); + } + + SeekerDroneUpdate(self); + + if (self->client->ps.powerups[PW_FORCE_BOON]) + { + prepower = self->client->ps.fd.forcePower; + } + + if (self && self->client && (BG_HasYsalamiri(g_gametype.integer, &self->client->ps) || + self->client->ps.fd.forceDeactivateAll || self->client->tempSpectate >= level.time)) + { //has ysalamiri.. or we want to forcefully stop all his active powers + i = 0; + + while (i < NUM_FORCE_POWERS) + { + if ((self->client->ps.fd.forcePowersActive & (1 << i)) && i != FP_LEVITATION) + { + WP_ForcePowerStop(self, i); + } + + i++; + } + + if (self->client->tempSpectate >= level.time) + { + self->client->ps.fd.forcePower = 100; + self->client->ps.fd.forceRageRecoveryTime = 0; + } + + self->client->ps.fd.forceDeactivateAll = 0; + + if (self->client->ps.fd.forceJumpCharge) + { + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE); + self->client->ps.fd.forceJumpCharge = 0; + } + } + else + { //otherwise just do a check through them all to see if they need to be stopped for any reason. + i = 0; + + while (i < NUM_FORCE_POWERS) + { + if ((self->client->ps.fd.forcePowersActive & (1 << i)) && i != FP_LEVITATION && + !BG_CanUseFPNow(g_gametype.integer, &self->client->ps, level.time, i)) + { + WP_ForcePowerStop(self, i); + } + + i++; + } + } + + i = 0; + + if (self->client->ps.powerups[PW_FORCE_ENLIGHTENED_LIGHT] || self->client->ps.powerups[PW_FORCE_ENLIGHTENED_DARK]) + { //enlightenment + if (!self->client->ps.fd.forceUsingAdded) + { + i = 0; + + while (i < NUM_FORCE_POWERS) + { + self->client->ps.fd.forcePowerBaseLevel[i] = self->client->ps.fd.forcePowerLevel[i]; + + if (!forcePowerDarkLight[i] || + self->client->ps.fd.forceSide == forcePowerDarkLight[i]) + { + self->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3; + self->client->ps.fd.forcePowersKnown |= (1 << i); + } + + i++; + } + + self->client->ps.fd.forceUsingAdded = 1; + } + } + else if (self->client->ps.fd.forceUsingAdded) + { //we don't have enlightenment but we're still using enlightened powers, so clear them back to how they should be. + i = 0; + + while (i < NUM_FORCE_POWERS) + { + self->client->ps.fd.forcePowerLevel[i] = self->client->ps.fd.forcePowerBaseLevel[i]; + if (!self->client->ps.fd.forcePowerLevel[i]) + { + if (self->client->ps.fd.forcePowersActive & (1 << i)) + { + WP_ForcePowerStop(self, i); + } + self->client->ps.fd.forcePowersKnown &= ~(1 << i); + } + + i++; + } + + self->client->ps.fd.forceUsingAdded = 0; + } + + i = 0; + + if (!(self->client->ps.fd.forcePowersActive & (1 << FP_TELEPATHY))) + { //clear the mindtrick index values + self->client->ps.fd.forceMindtrickTargetIndex = 0; + self->client->ps.fd.forceMindtrickTargetIndex2 = 0; + self->client->ps.fd.forceMindtrickTargetIndex3 = 0; + self->client->ps.fd.forceMindtrickTargetIndex4 = 0; + } + + if (self->health < 1) + { + self->client->ps.fd.forceGripBeingGripped = 0; + } + + if (self->client->ps.fd.forceGripBeingGripped > level.time) + { + self->client->ps.fd.forceGripCripple = 1; + + //keep the saber off during this period + if (self->client->ps.weapon == WP_SABER && !self->client->ps.saberHolstered) + { + Cmd_ToggleSaber_f(self); + } + } + else + { + self->client->ps.fd.forceGripCripple = 0; + } + + if (self->client->ps.fd.forceJumpSound) + { + G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEJUMP); + self->client->ps.fd.forceJumpSound = 0; + } + + if (self->client->ps.fd.forceGripCripple) + { + if (self->client->ps.fd.forceGripSoundTime < level.time) + { + G_PreDefSound(self->client->ps.origin, PDSOUND_FORCEGRIP); + self->client->ps.fd.forceGripSoundTime = level.time + 1000; + } + } + + if (self->client->ps.fd.forcePowersActive & (1 << FP_SPEED)) + { + self->client->ps.powerups[PW_SPEED] = level.time + 100; + } + + if ( self->health <= 0 ) + {//if dead, deactivate any active force powers + for ( i = 0; i < NUM_FORCE_POWERS; i++ ) + { + if ( self->client->ps.fd.forcePowerDuration[i] || (self->client->ps.fd.forcePowersActive&( 1 << i )) ) + { + WP_ForcePowerStop( self, (forcePowers_t)i ); + self->client->ps.fd.forcePowerDuration[i] = 0; + } + } + goto powersetcheck; + } + + if (self->client->ps.groundEntityNum != ENTITYNUM_NONE) + { + self->client->fjDidJump = qfalse; + } + + if (self->client->ps.fd.forceJumpCharge && self->client->ps.groundEntityNum == ENTITYNUM_NONE && self->client->fjDidJump) + { //this was for the "charge" jump method... I guess + if (ucmd->upmove < 10 && (!(ucmd->buttons & BUTTON_FORCEPOWER) || self->client->ps.fd.forcePowerSelected != FP_LEVITATION)) + { + G_MuteSound(self->client->ps.fd.killSoundEntIndex[TRACK_CHANNEL_1-50], CHAN_VOICE); + self->client->ps.fd.forceJumpCharge = 0; + } + } + +#ifndef METROID_JUMP + else if ( (ucmd->upmove > 10) && (self->client->ps.pm_flags & PMF_JUMP_HELD) && self->client->ps.groundTime && (level.time - self->client->ps.groundTime) > 150 && !BG_HasYsalamiri(g_gametype.integer, &self->client->ps) && BG_CanUseFPNow(g_gametype.integer, &self->client->ps, level.time, FP_LEVITATION) ) + {//just charging up + ForceJumpCharge( self, ucmd ); + usingForce = qtrue; + } + else if (ucmd->upmove < 10 && self->client->ps.groundEntityNum == ENTITYNUM_NONE && self->client->ps.fd.forceJumpCharge) + { + self->client->ps.pm_flags &= ~(PMF_JUMP_HELD); + } +#endif + + if (!(self->client->ps.pm_flags & PMF_JUMP_HELD) && self->client->ps.fd.forceJumpCharge) + { + if (!(ucmd->buttons & BUTTON_FORCEPOWER) || + self->client->ps.fd.forcePowerSelected != FP_LEVITATION) + { + if (WP_DoSpecificPower( self, ucmd, FP_LEVITATION )) + { + usingForce = qtrue; + } + } + } + + if ( ucmd->buttons & BUTTON_FORCEGRIP ) + { //grip is one of the powers with its own button.. if it's held, call the specific grip power function. + if (WP_DoSpecificPower( self, ucmd, FP_GRIP )) + { + usingForce = qtrue; + } + else + { //don't let recharge even if the grip misses if the player still has the button down + usingForce = qtrue; + } + } + else + { //see if we're using it generically.. if not, stop. + if (self->client->ps.fd.forcePowersActive & (1 << FP_GRIP)) + { + if (!(ucmd->buttons & BUTTON_FORCEPOWER) || self->client->ps.fd.forcePowerSelected != FP_GRIP) + { + WP_ForcePowerStop(self, FP_GRIP); + } + } + } + + if ( ucmd->buttons & BUTTON_FORCE_LIGHTNING ) + { //lightning + WP_DoSpecificPower(self, ucmd, FP_LIGHTNING); + usingForce = qtrue; + } + else + { //see if we're using it generically.. if not, stop. + if (self->client->ps.fd.forcePowersActive & (1 << FP_LIGHTNING)) + { + if (!(ucmd->buttons & BUTTON_FORCEPOWER) || self->client->ps.fd.forcePowerSelected != FP_LIGHTNING) + { + WP_ForcePowerStop(self, FP_LIGHTNING); + } + } + } + + if ( ucmd->buttons & BUTTON_FORCE_DRAIN ) + { //drain + WP_DoSpecificPower(self, ucmd, FP_DRAIN); + usingForce = qtrue; + } + else + { //see if we're using it generically.. if not, stop. + if (self->client->ps.fd.forcePowersActive & (1 << FP_DRAIN)) + { + if (!(ucmd->buttons & BUTTON_FORCEPOWER) || self->client->ps.fd.forcePowerSelected != FP_DRAIN) + { + WP_ForcePowerStop(self, FP_DRAIN); + } + } + } + + if ( (ucmd->buttons & BUTTON_FORCEPOWER) && + BG_CanUseFPNow(g_gametype.integer, &self->client->ps, level.time, self->client->ps.fd.forcePowerSelected)) + { + if (self->client->ps.fd.forcePowerSelected == FP_LEVITATION) + { + ForceJumpCharge( self, ucmd ); + usingForce = qtrue; + } + else if (WP_DoSpecificPower( self, ucmd, self->client->ps.fd.forcePowerSelected )) + { + usingForce = qtrue; + } + else if (self->client->ps.fd.forcePowerSelected == FP_GRIP) + { + usingForce = qtrue; + } + } + else + { + self->client->ps.fd.forceButtonNeedRelease = 0; + } + + for ( i = 0; i < NUM_FORCE_POWERS; i++ ) + { + if ( self->client->ps.fd.forcePowerDuration[i] ) + { + if ( self->client->ps.fd.forcePowerDuration[i] < level.time ) + { + if ( (self->client->ps.fd.forcePowersActive&( 1 << i )) ) + {//turn it off + WP_ForcePowerStop( self, (forcePowers_t)i ); + } + self->client->ps.fd.forcePowerDuration[i] = 0; + } + } + if ( (self->client->ps.fd.forcePowersActive&( 1 << i )) ) + { + usingForce = qtrue; + WP_ForcePowerRun( self, (forcePowers_t)i, ucmd ); + } + } + if ( self->client->ps.saberInFlight && self->client->ps.saberEntityNum ) + {//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 ( !self->client->ps.fd.forcePowersActive || self->client->ps.fd.forcePowersActive == (1 << FP_DRAIN) ) + {//when not using the force, regenerate at 1 point per half second + if ( !self->client->ps.saberInFlight && self->client->ps.fd.forcePowerRegenDebounceTime < level.time && + (self->client->ps.weapon != WP_SABER || !BG_SaberInSpecial(self->client->ps.saberMove)) ) + { + if (g_gametype.integer != GT_HOLOCRON || g_MaxHolocronCarry.value) + { + //if (!g_trueJedi.integer || self->client->ps.weapon == WP_SABER) + //let non-jedi force regen since we're doing a more strict jedi/non-jedi thing... this gives dark jedi something to drain + { + if (self->client->ps.powerups[PW_FORCE_BOON]) + { + WP_ForcePowerRegenerate( self, 6 ); + } + else if (self->client->ps.isJediMaster && g_gametype.integer == GT_JEDIMASTER) + { + WP_ForcePowerRegenerate( self, 4 ); //jedi master regenerates 4 times as fast + } + else + { + WP_ForcePowerRegenerate( self, 0 ); + } + } + /* + else if (g_trueJedi.integer && self->client->ps.weapon != WP_SABER) + { + self->client->ps.fd.forcePower = 0; + } + */ + } + else + { //regenerate based on the number of holocrons carried + holoregen = 0; + holo = 0; + while (holo < NUM_FORCE_POWERS) + { + if (self->client->ps.holocronsCarried[holo]) + { + holoregen++; + } + holo++; + } + + WP_ForcePowerRegenerate(self, holoregen); + } + + if (g_gametype.integer == GT_SIEGE) + { + if (self->client->holdingObjectiveItem && + g_entities[self->client->holdingObjectiveItem].inuse && + g_entities[self->client->holdingObjectiveItem].genericValue15) + { //1 point per 7 seconds.. super slow + self->client->ps.fd.forcePowerRegenDebounceTime = level.time + 7000; + } + else if (self->client->siegeClass != -1 && + (bgSiegeClasses[self->client->siegeClass].classflags & (1<client->ps.fd.forcePowerRegenDebounceTime = level.time + (g_forceRegenTime.integer*0.2); + } + else + { + self->client->ps.fd.forcePowerRegenDebounceTime = level.time + g_forceRegenTime.integer; + } + } + else + { + if ( g_gametype.integer == GT_POWERDUEL && self->client->sess.duelTeam == DUELTEAM_LONE ) + { + if ( g_duel_fraglimit.integer ) + { + self->client->ps.fd.forcePowerRegenDebounceTime = level.time + (g_forceRegenTime.integer* + (0.6 + (.3 * (float)self->client->sess.wins / (float)g_duel_fraglimit.integer))); + } + else + { + self->client->ps.fd.forcePowerRegenDebounceTime = level.time + (g_forceRegenTime.integer*0.7); + } + } + else + { + self->client->ps.fd.forcePowerRegenDebounceTime = level.time + g_forceRegenTime.integer; + } + } + } + } + +powersetcheck: + + if (prepower && self->client->ps.fd.forcePower < prepower) + { + int dif = ((prepower - self->client->ps.fd.forcePower)/2); + if (dif < 1) + { + dif = 1; + } + + self->client->ps.fd.forcePower = (prepower-dif); + } +} + +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 (!g_forceDodge.integer) + { + return qfalse; + } + + if (g_forceDodge.integer != 2) + { + if (!(self->client->ps.fd.forcePowersActive & (1 << FP_SEE))) + { + return qfalse; + } + } + + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//can't dodge in mid-air + return qfalse; + } + + if ( self->client->ps.weaponTime > 0 || self->client->ps.forceHandExtend != HANDEXTEND_NONE ) + {//in some effect that stops me from moving on my own + return qfalse; + } + + if (g_forceDodge.integer == 2) + { + if (self->client->ps.fd.forcePowersActive) + { //for now just don't let us dodge if we're using a force power at all + return qfalse; + } + } + + if (g_forceDodge.integer == 2) + { + if ( !WP_ForcePowerUsable( self, FP_SPEED ) ) + {//make sure we have it and have enough force power + return qfalse; + } + } + + if (g_forceDodge.integer == 2) + { + if ( Q_irand( 1, 7 ) > self->client->ps.fd.forcePowerLevel[FP_SPEED] ) + {//more likely to fail on lower force speed level + return qfalse; + } + } + else + { + //We now dodge all the time, but only on level 3 + if (self->client->ps.fd.forcePowerLevel[FP_SEE] < FORCE_LEVEL_3) + {//more likely to fail on lower force sight level + return qfalse; + } + } + + switch( hitLoc ) + { + case HL_NONE: + return qfalse; + break; + + case HL_FOOT_RT: + case HL_FOOT_LT: + case HL_LEG_RT: + case HL_LEG_LT: + return qfalse; + + case HL_BACK_RT: + dodgeAnim = BOTH_DODGE_FL; + break; + case HL_CHEST_RT: + dodgeAnim = BOTH_DODGE_FR; + break; + case HL_BACK_LT: + dodgeAnim = BOTH_DODGE_FR; + break; + case HL_CHEST_LT: + dodgeAnim = BOTH_DODGE_FR; + break; + case HL_BACK: + case HL_CHEST: + case HL_WAIST: + dodgeAnim = BOTH_DODGE_FL; + 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 = BOTH_DODGE_FL; + break; + default: + return qfalse; + } + + if ( dodgeAnim != -1 ) + { + //Our own happy way of forcing an anim: + self->client->ps.forceHandExtend = HANDEXTEND_DODGE; + self->client->ps.forceDodgeAnim = dodgeAnim; + self->client->ps.forceHandExtendTime = level.time + 300; + + self->client->ps.powerups[PW_SPEEDBURST] = level.time + 100; + + if (g_forceDodge.integer == 2) + { + ForceSpeed( self, 500 ); + } + else + { + G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/speed.wav") ); + } + return qtrue; + } + return qfalse; +} diff --git a/codemp/game/w_saber.c b/codemp/game/w_saber.c new file mode 100644 index 0000000..ab3e1c7 --- /dev/null +++ b/codemp/game/w_saber.c @@ -0,0 +1,8957 @@ +#include "g_local.h" +#include "bg_local.h" +#include "w_saber.h" +#include "ai_main.h" +#include "../ghoul2/G2.h" + +#define SABER_BOX_SIZE 16.0f +extern bot_state_t *botstates[MAX_CLIENTS]; +extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold ); +extern void G_TestLine(vec3_t start, vec3_t end, int color, int time); + +extern vmCvar_t g_saberRealisticCombat; +extern vmCvar_t d_saberSPStyleDamage; +extern vmCvar_t g_debugSaberLocks; +// nmckenzie: SABER_DAMAGE_WALLS +extern vmCvar_t g_saberWallDamageScale; + +int saberSpinSound = 0; + +//would be cleaner if these were renamed to BG_ and proto'd in a header. +#include "../namespace_begin.h" +qboolean PM_SaberInTransition( int move ); +qboolean PM_SaberInDeflect( int move ); +qboolean PM_SaberInBrokenParry( int move ); +qboolean PM_SaberInBounce( int move ); +qboolean BG_SaberInReturn( int move ); +qboolean BG_InKnockDownOnGround( playerState_t *ps ); +qboolean BG_StabDownAnim( int anim ); +qboolean BG_SabersOff( playerState_t *ps ); +#include "../namespace_end.h" + +void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin ); +void WP_SaberRemoveG2Model( gentity_t *saberent ); + +float RandFloat(float min, float max) { + return ((rand() * (max - min)) / 32768.0F) + min; +} + +#ifdef DEBUG_SABER_BOX +void G_DebugBoxLines(vec3_t mins, vec3_t maxs, int duration) +{ + vec3_t start; + vec3_t end; + + float x = maxs[0] - mins[0]; + float y = maxs[1] - mins[1]; + + // top of box + VectorCopy(maxs, start); + VectorCopy(maxs, end); + start[0] -= x; + G_TestLine(start, end, 0x00000ff, duration); + end[0] = start[0]; + end[1] -= y; + G_TestLine(start, end, 0x00000ff, duration); + start[1] = end[1]; + start[0] += x; + G_TestLine(start, end, 0x00000ff, duration); + G_TestLine(start, maxs, 0x00000ff, duration); + // bottom of box + VectorCopy(mins, start); + VectorCopy(mins, end); + start[0] += x; + G_TestLine(start, end, 0x00000ff, duration); + end[0] = start[0]; + end[1] += y; + G_TestLine(start, end, 0x00000ff, duration); + start[1] = end[1]; + start[0] -= x; + G_TestLine(start, end, 0x00000ff, duration); + G_TestLine(start, mins, 0x00000ff, duration); +} +#endif + +//general check for performing certain attacks against others +qboolean G_CanBeEnemy(gentity_t *self, gentity_t *enemy) +{ + if (!self->inuse || !enemy->inuse || !self->client || !enemy->client) + { + return qfalse; + } + + if (self->client->ps.duelInProgress && self->client->ps.duelIndex != enemy->s.number) + { //dueling but not with this person + return qfalse; + } + + if (enemy->client->ps.duelInProgress && enemy->client->ps.duelIndex != self->s.number) + { //other guy dueling but not with me + return qfalse; + } + + if (g_gametype.integer < GT_TEAM) + { //ok, sure + return qtrue; + } + + if (g_friendlyFire.integer) + { //if ff on then can inflict damage normally on teammates + return qtrue; + } + + if (OnSameTeam(self, enemy)) + { //ff not on, don't hurt teammates + return qfalse; + } + + return qtrue; +} + +//This function gets the attack power which is used to decide broken parries, +//knockaways, and numerous other things. It is not directly related to the +//actual amount of damage done, however. -rww +static GAME_INLINE int G_SaberAttackPower(gentity_t *ent, qboolean attacking) +{ + int baseLevel; + assert(ent && ent->client); + + baseLevel = ent->client->ps.fd.saberAnimLevel; + + //Give "medium" strength for the two special stances. + if (baseLevel == SS_DUAL) + { + baseLevel = 2; + } + else if (baseLevel == SS_STAFF) + { + baseLevel = 2; + } + + if (attacking) + { //the attacker gets a boost to help penetrate defense. + //General boost up so the individual levels make a bigger difference. + baseLevel *= 2; + + baseLevel++; + + //Get the "speed" of the swing, roughly, and add more power + //to the attack based on it. + if (ent->client->lastSaberStorageTime >= (level.time-50) && + ent->client->olderIsValid) + { + vec3_t vSub; + int swingDist; + int toleranceAmt; + + //We want different "tolerance" levels for adding in the distance of the last swing + //to the base power level depending on which stance we are using. Otherwise fast + //would have more advantage than it should since the animations are all much faster. + switch (ent->client->ps.fd.saberAnimLevel) + { + case SS_STRONG: + toleranceAmt = 8; + break; + case SS_MEDIUM: + toleranceAmt = 16; + break; + case SS_FAST: + toleranceAmt = 24; + break; + default: //dual, staff, etc. + toleranceAmt = 16; + break; + } + + VectorSubtract(ent->client->lastSaberBase_Always, ent->client->olderSaberBase, vSub); + swingDist = (int)VectorLength(vSub); + + while (swingDist > 0) + { //I would like to do something more clever. But I suppose this works, at least for now. + baseLevel++; + swingDist -= toleranceAmt; + } + } + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer > 1) + { + Com_Printf("Client %i: ATT STR: %i\n", ent->s.number, baseLevel); + } +#endif + } + + if ((ent->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) || + (ent->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM))) + { //We're very weak when one of our arms is broken + baseLevel *= 0.3; + } + + //Cap at reasonable values now. + if (baseLevel < 1) + { + baseLevel = 1; + } + else if (baseLevel > 16) + { + baseLevel = 16; + } + + if (g_gametype.integer == GT_POWERDUEL && + ent->client->sess.duelTeam == DUELTEAM_LONE) + { //get more power then + return baseLevel*2; + } + else if (attacking && g_gametype.integer == GT_SIEGE) + { //in siege, saber battles should be quicker and more biased toward the attacker + return baseLevel*3; + } + + return baseLevel; +} + +void WP_DeactivateSaber( gentity_t *self, qboolean clearLength ) +{ + if ( !self || !self->client ) + { + return; + } + //keep my saber off! + if ( !self->client->ps.saberHolstered ) + { + self->client->ps.saberHolstered = 2; + /* + if ( clearLength ) + { + self->client->ps.SetSaberLength( 0 ); + } + */ + //Doens't matter ATM + if (self->client->saber[0].soundOff) + { + G_Sound(self, CHAN_WEAPON, self->client->saber[0].soundOff); + } + + if (self->client->saber[1].soundOff && + self->client->saber[1].model[0]) + { + G_Sound(self, CHAN_WEAPON, self->client->saber[1].soundOff); + } + + } +} + +void WP_ActivateSaber( gentity_t *self ) +{ + if ( !self || !self->client ) + { + return; + } + + if (self->NPC && + self->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT && + (self->client->ps.forceHandExtendTime - level.time) > 200) + { //if we're an NPC and in the middle of a taunt then stop it + self->client->ps.forceHandExtend = HANDEXTEND_NONE; + self->client->ps.forceHandExtendTime = 0; + } + else if (self->client->ps.fd.forceGripCripple) + { //can't activate saber while being gripped + return; + } + + if ( self->client->ps.saberHolstered ) + { + self->client->ps.saberHolstered = 0; + if (self->client->saber[0].soundOn) + { + G_Sound(self, CHAN_WEAPON, self->client->saber[0].soundOn); + } + + if (self->client->saber[1].soundOn) + { + G_Sound(self, CHAN_WEAPON, self->client->saber[1].soundOn); + } + } +} + +#define PROPER_THROWN_VALUE 999 //Ah, well.. + +void SaberUpdateSelf(gentity_t *ent) +{ + if (ent->r.ownerNum == ENTITYNUM_NONE) + { + ent->think = G_FreeEntity; + ent->nextthink = level.time; + return; + } + + if (!g_entities[ent->r.ownerNum].inuse || + !g_entities[ent->r.ownerNum].client/* || + g_entities[ent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR*/) + { + ent->think = G_FreeEntity; + ent->nextthink = level.time; + return; + } + + if (g_entities[ent->r.ownerNum].client->ps.saberInFlight && g_entities[ent->r.ownerNum].health > 0) + { //let The Master take care of us now (we'll get treated like a missile until we return) + ent->nextthink = level.time; + ent->genericValue5 = PROPER_THROWN_VALUE; + return; + } + + ent->genericValue5 = 0; + + if (g_entities[ent->r.ownerNum].client->ps.weapon != WP_SABER || + (g_entities[ent->r.ownerNum].client->ps.pm_flags & PMF_FOLLOW) || + //RWW ADDED 7-19-03 BEGIN + g_entities[ent->r.ownerNum].client->sess.sessionTeam == TEAM_SPECTATOR || + g_entities[ent->r.ownerNum].client->tempSpectate >= level.time || + //RWW ADDED 7-19-03 END + g_entities[ent->r.ownerNum].health < 1 || + BG_SabersOff( &g_entities[ent->r.ownerNum].client->ps ) || + (!g_entities[ent->r.ownerNum].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] && g_entities[ent->r.ownerNum].s.eType != ET_NPC)) + { //owner is not using saber, spectating, dead, saber holstered, or has no attack level + ent->r.contents = 0; + ent->clipmask = 0; + } + else + { //Standard contents (saber is active) +#ifdef DEBUG_SABER_BOX + if (g_saberDebugBox.integer == 1|| g_saberDebugBox.integer == 4) + { + vec3_t dbgMins; + vec3_t dbgMaxs; + + VectorAdd( ent->r.currentOrigin, ent->r.mins, dbgMins ); + VectorAdd( ent->r.currentOrigin, ent->r.maxs, dbgMaxs ); + + G_DebugBoxLines(dbgMins, dbgMaxs, (10.0f/(float)g_svfps.integer)*100); + } +#endif + if (ent->r.contents != CONTENTS_LIGHTSABER) + { + if ((level.time - g_entities[ent->r.ownerNum].client->lastSaberStorageTime) <= 200) + { //Only go back to solid once we're sure our owner has updated recently + ent->r.contents = CONTENTS_LIGHTSABER; + ent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER; + } + } + else + { + ent->r.contents = CONTENTS_LIGHTSABER; + ent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER; + } + } + + trap_LinkEntity(ent); + + ent->nextthink = level.time; +} + +void SaberGotHit( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + gentity_t *own = &g_entities[self->r.ownerNum]; + + if (!own || !own->client) + { + return; + } + + //Do something here..? Was handling projectiles here, but instead they're now handled in their own functions. +} + +#include "../namespace_begin.h" +qboolean BG_SuperBreakLoseAnim( int anim ); +#include "../namespace_end.h" + +static GAME_INLINE void SetSaberBoxSize(gentity_t *saberent) +{ + gentity_t *owner = NULL; + vec3_t saberOrg, saberTip; + int i; + int j = 0; + int k = 0; + + assert(saberent && saberent->inuse); + + if (saberent->r.ownerNum < MAX_CLIENTS && saberent->r.ownerNum >= 0) + { + owner = &g_entities[saberent->r.ownerNum]; + } + else if (saberent->r.ownerNum >= 0 && saberent->r.ownerNum < ENTITYNUM_WORLD && + g_entities[saberent->r.ownerNum].s.eType == ET_NPC) + { + owner = &g_entities[saberent->r.ownerNum]; + } + + if (!owner || !owner->inuse || !owner->client) + { + assert(!"Saber with no owner?"); + return; + } + + if ( PM_SaberInBrokenParry(owner->client->ps.saberMove) + || BG_SuperBreakLoseAnim( owner->client->ps.torsoAnim ) ) + { //let swings go right through when we're in this state + VectorSet( saberent->r.mins, 0, 0, 0 ); + VectorSet( saberent->r.maxs, 0, 0, 0 ); +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer > 1) + { + Com_Printf("Client %i in broken parry, saber box 0\n", owner->s.number); + } +#endif + return; + } + + if ((level.time - owner->client->lastSaberStorageTime) > 200 || + (level.time - owner->client->saber[j].blade[k].storageTime) > 100) + { //it's been too long since we got a reliable point storage, so use the defaults and leave. + VectorSet( saberent->r.mins, -SABER_BOX_SIZE, -SABER_BOX_SIZE, -SABER_BOX_SIZE ); + VectorSet( saberent->r.maxs, SABER_BOX_SIZE, SABER_BOX_SIZE, SABER_BOX_SIZE ); + return; + } + + //Start out at the saber origin, then go through all the blades and push out the extents + //for each blade, then set the box relative to the origin. + VectorCopy(saberent->r.currentOrigin, saberent->r.mins); + VectorCopy(saberent->r.currentOrigin, saberent->r.maxs); + + for (i = 0; i < 3; i++) + { + for (j = 0; j < MAX_SABERS; j++) + { + if (!owner->client->saber[j].model[0]) + { + break; + } + + for (k = 0; k < owner->client->saber[j].numBlades; k++) + { + //VectorMA(owner->client->saber[j].blade[k].muzzlePoint, owner->client->saber[j].blade[k].lengthMax*0.5f, owner->client->saber[j].blade[k].muzzleDir, saberOrg); + VectorCopy(owner->client->saber[j].blade[k].muzzlePoint, saberOrg); + VectorMA(owner->client->saber[j].blade[k].muzzlePoint, owner->client->saber[j].blade[k].lengthMax, owner->client->saber[j].blade[k].muzzleDir, saberTip); + + if (saberOrg[i] < saberent->r.mins[i]) + { + saberent->r.mins[i] = saberOrg[i]; + } + if (saberTip[i] < saberent->r.mins[i]) + { + saberent->r.mins[i] = saberTip[i]; + } + + if (saberOrg[i] > saberent->r.maxs[i]) + { + saberent->r.maxs[i] = saberOrg[i]; + } + if (saberTip[i] > saberent->r.maxs[i]) + { + saberent->r.maxs[i] = saberTip[i]; + } + + //G_TestLine(saberOrg, saberTip, 0x0000ff, 50); + } + } + } + + VectorSubtract(saberent->r.mins, saberent->r.currentOrigin, saberent->r.mins); + VectorSubtract(saberent->r.maxs, saberent->r.currentOrigin, saberent->r.maxs); +} + +void WP_SaberInitBladeData( gentity_t *ent ) +{ + gentity_t *saberent = NULL; + gentity_t *checkEnt; + int i = 0; + + while (i < level.num_entities) + { //make sure there are no other saber entities floating around that think they belong to this client. + checkEnt = &g_entities[i]; + + if (checkEnt->inuse && checkEnt->neverFree && + checkEnt->r.ownerNum == ent->s.number && + checkEnt->classname && checkEnt->classname[0] && + !Q_stricmp(checkEnt->classname, "lightsaber")) + { + if (saberent) + { //already have one + checkEnt->neverFree = qfalse; + checkEnt->think = G_FreeEntity; + checkEnt->nextthink = level.time; + } + else + { //hmm.. well then, take it as my own. + //free the bitch but don't issue a kg2 to avoid overflowing clients. + checkEnt->s.modelGhoul2 = 0; + G_FreeEntity(checkEnt); + + //now init it manually and reuse this ent slot. + G_InitGentity(checkEnt); + saberent = checkEnt; + } + } + + i++; + } + + //We do not want the client to have any real knowledge of the entity whatsoever. It will only + //ever be used on the server. + if (!saberent) + { //ok, make one then + saberent = G_Spawn(); + } + ent->client->ps.saberEntityNum = ent->client->saberStoredIndex = saberent->s.number; + saberent->classname = "lightsaber"; + + saberent->neverFree = qtrue; //the saber being removed would be a terrible thing. + + saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + saberent->r.ownerNum = ent->s.number; + + saberent->clipmask = MASK_PLAYERSOLID | CONTENTS_LIGHTSABER; + saberent->r.contents = CONTENTS_LIGHTSABER; + + SetSaberBoxSize(saberent); + + saberent->mass = 10; + + saberent->s.eFlags |= EF_NODRAW; + saberent->r.svFlags |= SVF_NOCLIENT; + + saberent->s.modelGhoul2 = 1; + //should we happen to be removed (we belong to an NPC and he is removed) then + //we want to attempt to remove our g2 instance on the client in case we had one. + + saberent->touch = SaberGotHit; + + saberent->think = SaberUpdateSelf; + saberent->genericValue5 = 0; + saberent->nextthink = level.time + 50; + + saberSpinSound = G_SoundIndex("sound/weapons/saber/saberspin.wav"); +} + +#define LOOK_DEFAULT_SPEED 0.15f +#define LOOK_TALKING_SPEED 0.15f + +static GAME_INLINE qboolean G_CheckLookTarget( gentity_t *ent, vec3_t lookAngles, float *lookingSpeed ) +{ + //FIXME: also clamp the lookAngles based on the clamp + the existing difference between + // headAngles and torsoAngles? But often the tag_torso is straight but the torso itself + // is deformed to not face straight... sigh... + + if (ent->s.eType == ET_NPC && + ent->s.m_iVehicleNum && + ent->s.NPC_class != CLASS_VEHICLE ) + { //an NPC bolted to a vehicle should just look around randomly + if ( TIMER_Done( ent, "lookAround" ) ) + { + ent->NPC->shootAngles[YAW] = flrand(0,360); + TIMER_Set( ent, "lookAround", Q_irand( 500, 3000 ) ); + } + VectorSet( lookAngles, 0, ent->NPC->shootAngles[YAW], 0 ); + return qtrue; + } + //Now calc head angle to lookTarget, if any + if ( ent->client->renderInfo.lookTarget >= 0 && ent->client->renderInfo.lookTarget < ENTITYNUM_WORLD ) + { + vec3_t lookDir, lookOrg, eyeOrg; + int i; + + if ( ent->client->renderInfo.lookMode == LM_ENT ) + { + gentity_t *lookCent = &g_entities[ent->client->renderInfo.lookTarget]; + if ( lookCent ) + { + if ( lookCent != ent->enemy ) + {//We turn heads faster than headbob speed, but not as fast as if watching an enemy + *lookingSpeed = LOOK_DEFAULT_SPEED; + } + + //FIXME: Ignore small deltas from current angles so we don't bob our head in synch with theirs? + + /* + if ( ent->client->renderInfo.lookTarget == 0 && !cg.renderingThirdPerson )//!cg_thirdPerson.integer ) + {//Special case- use cg.refdef.vieworg if looking at player and not in third person view + VectorCopy( cg.refdef.vieworg, lookOrg ); + } + */ //No no no! + if ( lookCent->client ) + { + VectorCopy( lookCent->client->renderInfo.eyePoint, lookOrg ); + } + else if ( lookCent->inuse && !VectorCompare( lookCent->r.currentOrigin, vec3_origin ) ) + { + VectorCopy( lookCent->r.currentOrigin, lookOrg ); + } + else + {//at origin of world + return qfalse; + } + //Look in dir of lookTarget + } + } + else if ( ent->client->renderInfo.lookMode == LM_INTEREST && ent->client->renderInfo.lookTarget > -1 && ent->client->renderInfo.lookTarget < MAX_INTEREST_POINTS ) + { + VectorCopy( level.interestPoints[ent->client->renderInfo.lookTarget].origin, lookOrg ); + } + else + { + return qfalse; + } + + VectorCopy( ent->client->renderInfo.eyePoint, eyeOrg ); + + VectorSubtract( lookOrg, eyeOrg, lookDir ); + + vectoangles( lookDir, lookAngles ); + + for ( i = 0; i < 3; i++ ) + { + lookAngles[i] = AngleNormalize180( lookAngles[i] ); + ent->client->renderInfo.eyeAngles[i] = AngleNormalize180( ent->client->renderInfo.eyeAngles[i] ); + } + AnglesSubtract( lookAngles, ent->client->renderInfo.eyeAngles, lookAngles ); + return qtrue; + } + + return qfalse; +} + +//rww - attempted "port" of the SP version which is completely client-side and +//uses illegal gentity access. I am trying to keep this from being too +//bandwidth-intensive. +//This is primarily droid stuff I guess, I'm going to try to handle all humanoid +//NPC stuff in with the actual player stuff if possible. +void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles); +static GAME_INLINE void G_G2NPCAngles(gentity_t *ent, vec3_t legs[3], vec3_t angles) +{ + char *craniumBone = "cranium"; + char *thoracicBone = "thoracic"; //only used by atst so doesn't need a case + qboolean looking = qfalse; + vec3_t viewAngles; + vec3_t lookAngles; + + if ( ent->client ) + { + if ( (ent->client->NPC_class == CLASS_PROBE ) + || (ent->client->NPC_class == CLASS_R2D2 ) + || (ent->client->NPC_class == CLASS_R5D2) + || (ent->client->NPC_class == CLASS_ATST) ) + { + vec3_t trailingLegsAngles; + + if (ent->s.eType == ET_NPC && + ent->s.m_iVehicleNum && + ent->s.NPC_class != CLASS_VEHICLE ) + { //an NPC bolted to a vehicle should use the full angles + VectorCopy(ent->r.currentAngles, angles); + } + else + { + VectorCopy( ent->client->ps.viewangles, angles ); + angles[PITCH] = 0; + } + + //FIXME: use actual swing/clamp tolerances? + /* + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//on the ground + CG_PlayerLegsYawFromMovement( cent, ent->client->ps.velocity, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue ); + } + else + {//face legs to front + CG_PlayerLegsYawFromMovement( cent, vec3_origin, &angles[YAW], cent->lerpAngles[YAW], -60, 60, qtrue ); + } + */ + + VectorCopy( ent->client->ps.viewangles, viewAngles ); + // viewAngles[YAW] = viewAngles[ROLL] = 0; + viewAngles[PITCH] *= 0.5; + VectorCopy( viewAngles, lookAngles ); + + lookAngles[1] = 0; + + if ( ent->client->NPC_class == CLASS_ATST ) + {//body pitch + NPC_SetBoneAngles(ent, thoracicBone, lookAngles); + //BG_G2SetBoneAngles( cent, ent, ent->thoracicBone, lookAngles, BONE_ANGLES_POSTMULT,POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw); + } + + VectorCopy( viewAngles, lookAngles ); + + if ( ent && ent->client && ent->client->NPC_class == CLASS_ATST ) + { + //CG_ATSTLegsYaw( cent, trailingLegsAngles ); + AnglesToAxis( trailingLegsAngles, legs ); + } + else + { + //FIXME: this needs to properly set the legs.yawing field so we don't erroneously play the turning anim, but we do play it when turning in place + /* + if ( angles[YAW] == cent->pe.legs.yawAngle ) + { + cent->pe.legs.yawing = qfalse; + } + else + { + cent->pe.legs.yawing = qtrue; + } + + cent->pe.legs.yawAngle = angles[YAW]; + if ( ent->client ) + { + ent->client->renderInfo.legsYaw = angles[YAW]; + } + AnglesToAxis( angles, legs ); + */ + } + + // if ( ent && ent->client && ent->client->NPC_class == CLASS_ATST ) + // { + // looking = qfalse; + // } + // else + { //look at lookTarget! + //FIXME: snaps to side when lets go of lookTarget... ? + float lookingSpeed = 0.3f; + looking = G_CheckLookTarget( ent, lookAngles, &lookingSpeed ); + lookAngles[PITCH] = lookAngles[ROLL] = 0;//droids can't pitch or roll their heads + if ( looking ) + {//want to keep doing this lerp behavior for a full second after stopped looking (so don't snap) + ent->client->renderInfo.lookingDebounceTime = level.time + 1000; + } + } + if ( ent->client->renderInfo.lookingDebounceTime > level.time ) + { //adjust for current body orientation + vec3_t oldLookAngles; + + lookAngles[YAW] -= 0;//ent->client->ps.viewangles[YAW];//cent->pe.torso.yawAngle; + //lookAngles[YAW] -= cent->pe.legs.yawAngle; + + //normalize + lookAngles[YAW] = AngleNormalize180( lookAngles[YAW] ); + + //slowly lerp to this new value + //Remember last headAngles + VectorCopy( ent->client->renderInfo.lastHeadAngles, oldLookAngles ); + if( VectorCompare( oldLookAngles, lookAngles ) == qfalse ) + { + //FIXME: This clamp goes off viewAngles, + //but really should go off the tag_torso's axis[0] angles, no? + lookAngles[YAW] = oldLookAngles[YAW]+(lookAngles[YAW]-oldLookAngles[YAW])*0.4f; + } + //Remember current lookAngles next time + VectorCopy( lookAngles, ent->client->renderInfo.lastHeadAngles ); + } + else + {//Remember current lookAngles next time + VectorCopy( lookAngles, ent->client->renderInfo.lastHeadAngles ); + } + if ( ent->client->NPC_class == CLASS_ATST ) + { + VectorCopy( ent->client->ps.viewangles, lookAngles ); + lookAngles[0] = lookAngles[2] = 0; + lookAngles[YAW] -= trailingLegsAngles[YAW]; + } + else + { + lookAngles[PITCH] = lookAngles[ROLL] = 0; + lookAngles[YAW] -= ent->client->ps.viewangles[YAW]; + } + + NPC_SetBoneAngles(ent, craniumBone, lookAngles); + //BG_G2SetBoneAngles( cent, ent, ent->craniumBone, lookAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.model_draw); + //return; + } + else//if ( (ent->client->NPC_class == CLASS_GONK ) || (ent->client->NPC_class == CLASS_INTERROGATOR) || (ent->client->NPC_class == CLASS_SENTRY) ) + { + // VectorCopy( ent->client->ps.viewangles, angles ); + // AnglesToAxis( angles, legs ); + //return; + } + } +} + +static GAME_INLINE void G_G2PlayerAngles( gentity_t *ent, vec3_t legs[3], vec3_t legsAngles) +{ + qboolean tPitching = qfalse, + tYawing = qfalse, + lYawing = qfalse; + float tYawAngle = ent->client->ps.viewangles[YAW], + tPitchAngle = 0, + lYawAngle = ent->client->ps.viewangles[YAW]; + + int ciLegs = ent->client->ps.legsAnim; + int ciTorso = ent->client->ps.torsoAnim; + + vec3_t turAngles; + vec3_t lerpOrg, lerpAng; + + if (ent->s.eType == ET_NPC && ent->client) + { //sort of hacky, but it saves a pretty big load off the server + int i = 0; + gentity_t *clEnt; + + //If no real clients are in the same PVS then don't do any of this stuff, no one can see him anyway! + while (i < MAX_CLIENTS) + { + clEnt = &g_entities[i]; + + if (clEnt && clEnt->inuse && clEnt->client && + trap_InPVS(clEnt->client->ps.origin, ent->client->ps.origin)) + { //this client can see him + break; + } + + i++; + } + + if (i == MAX_CLIENTS) + { //no one can see him, just return + return; + } + } + + VectorCopy(ent->client->ps.origin, lerpOrg); + VectorCopy(ent->client->ps.viewangles, lerpAng); + + if (ent->localAnimIndex <= 1) + { //don't do these things on non-humanoids + vec3_t lookAngles; + entityState_t *emplaced = NULL; + + if (ent->client->ps.hasLookTarget) + { + VectorSubtract(g_entities[ent->client->ps.lookTarget].r.currentOrigin, ent->client->ps.origin, lookAngles); + vectoangles(lookAngles, lookAngles); + ent->client->lookTime = level.time + 1000; + } + else + { + VectorCopy(ent->client->ps.origin, lookAngles); + } + lookAngles[PITCH] = 0; + + if (ent->client->ps.emplacedIndex) + { + emplaced = &g_entities[ent->client->ps.emplacedIndex].s; + } + + BG_G2PlayerAngles(ent->ghoul2, ent->client->renderInfo.motionBolt, &ent->s, level.time, lerpOrg, lerpAng, legs, + legsAngles, &tYawing, &tPitching, &lYawing, &tYawAngle, &tPitchAngle, &lYawAngle, FRAMETIME, turAngles, + ent->modelScale, ciLegs, ciTorso, &ent->client->corrTime, lookAngles, ent->client->lastHeadAngles, + ent->client->lookTime, emplaced, NULL); + + if (ent->client->ps.heldByClient && ent->client->ps.heldByClient <= MAX_CLIENTS) + { //then put our arm in this client's hand + //is index+1 because index 0 is valid. + int heldByIndex = ent->client->ps.heldByClient-1; + gentity_t *other = &g_entities[heldByIndex]; + int lHandBolt = 0; + + if (other && other->inuse && other->client && other->ghoul2) + { + lHandBolt = trap_G2API_AddBolt(other->ghoul2, 0, "*l_hand"); + } + else + { //they left the game, perhaps? + ent->client->ps.heldByClient = 0; + return; + } + + if (lHandBolt) + { + mdxaBone_t boltMatrix; + vec3_t boltOrg; + vec3_t tAngles; + + VectorCopy(other->client->ps.viewangles, tAngles); + tAngles[PITCH] = tAngles[ROLL] = 0; + + trap_G2API_GetBoltMatrix(other->ghoul2, 0, lHandBolt, &boltMatrix, tAngles, other->client->ps.origin, level.time, 0, other->modelScale); + boltOrg[0] = boltMatrix.matrix[0][3]; + boltOrg[1] = boltMatrix.matrix[1][3]; + boltOrg[2] = boltMatrix.matrix[2][3]; + + BG_IK_MoveArm(ent->ghoul2, lHandBolt, level.time, &ent->s, ent->client->ps.torsoAnim/*BOTH_DEAD1*/, boltOrg, &ent->client->ikStatus, + ent->client->ps.origin, ent->client->ps.viewangles, ent->modelScale, 500, qfalse); + } + } + else if (ent->client->ikStatus) + { //make sure we aren't IKing if we don't have anyone to hold onto us. + int lHandBolt = 0; + + if (ent && ent->inuse && ent->client && ent->ghoul2) + { + lHandBolt = trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand"); + } + else + { //This shouldn't happen, but just in case it does, we'll have a failsafe. + ent->client->ikStatus = qfalse; + } + + if (lHandBolt) + { + BG_IK_MoveArm(ent->ghoul2, lHandBolt, level.time, &ent->s, + ent->client->ps.torsoAnim/*BOTH_DEAD1*/, vec3_origin, &ent->client->ikStatus, ent->client->ps.origin, ent->client->ps.viewangles, ent->modelScale, 500, qtrue); + } + } + } + else if ( ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + { + vec3_t lookAngles; + + VectorCopy(ent->client->ps.viewangles, legsAngles); + legsAngles[PITCH] = 0; + AnglesToAxis( legsAngles, legs ); + + VectorCopy(ent->client->ps.viewangles, lookAngles); + lookAngles[YAW] = lookAngles[ROLL] = 0; + + BG_G2ATSTAngles( ent->ghoul2, level.time, lookAngles ); + } + else if (ent->NPC) + { //an NPC not using a humanoid skeleton, do special angle stuff. + if (ent->s.eType == ET_NPC && + ent->s.NPC_class == CLASS_VEHICLE && + ent->m_pVehicle && + ent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //fighters actually want to take pitch and roll into account for the axial angles + VectorCopy(ent->client->ps.viewangles, legsAngles); + AnglesToAxis( legsAngles, legs ); + } + else + { + G_G2NPCAngles(ent, legs, legsAngles); + } + } +} + +static GAME_INLINE qboolean SaberAttacking(gentity_t *self) +{ + if (PM_SaberInParry(self->client->ps.saberMove)) + { + return qfalse; + } + if (PM_SaberInBrokenParry(self->client->ps.saberMove)) + { + return qfalse; + } + if (PM_SaberInDeflect(self->client->ps.saberMove)) + { + return qfalse; + } + if (PM_SaberInBounce(self->client->ps.saberMove)) + { + return qfalse; + } + if (PM_SaberInKnockaway(self->client->ps.saberMove)) + { + return qfalse; + } + + if (BG_SaberInAttack(self->client->ps.saberMove)) + { + if (self->client->ps.weaponstate == WEAPON_FIRING && self->client->ps.saberBlocked == BLOCKED_NONE) + { //if we're firing and not blocking, then we're attacking. + return qtrue; + } + } + + if (BG_SaberInSpecial(self->client->ps.saberMove)) + { + return qtrue; + } + + return qfalse; +} + +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 +} sabersLockMode_t; + +#define LOCK_IDEAL_DIST_TOP 32.0f +#define LOCK_IDEAL_DIST_CIRCLE 48.0f + +#define SABER_HITDAMAGE 35 +void WP_SaberBlockNonRandom( gentity_t *self, vec3_t hitloc, qboolean missileBlock ); + +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; +} + +#include "../namespace_begin.h" +extern qboolean BG_CheckIncrementLockAnim( int anim, int winOrLose ); //bg_saber.c +#include "../namespace_end.h" +#define LOCK_IDEAL_DIST_JKA 46.0f//all of the new saberlocks are 46.08 from each other because Richard Lico is da MAN + +static GAME_INLINE qboolean WP_SabersCheckLock2( gentity_t *attacker, gentity_t *defender, sabersLockMode_t lockMode ) +{ + int attAnim, defAnim = 0; + float attStart = 0.5f, defStart = 0.5f; + float idealDist = 48.0f; + vec3_t attAngles, defAngles, defDir; + vec3_t newOrg; + vec3_t attDir; + float diff = 0; + trace_t trace; + + //MATCH ANIMS + if ( lockMode == LOCK_RANDOM ) + { + lockMode = (sabersLockMode_t)Q_irand( (int)LOCK_FIRST, (int)(LOCK_RANDOM)-1 ); + } + if ( attacker->client->ps.fd.saberAnimLevel >= SS_FAST + && attacker->client->ps.fd.saberAnimLevel <= SS_TAVION + && defender->client->ps.fd.saberAnimLevel >= SS_FAST + && defender->client->ps.fd.saberAnimLevel <= SS_TAVION ) + {//2 single sabers? Just do it the old way... + switch ( lockMode ) + { + case LOCK_TOP: + attAnim = BOTH_BF2LOCK; + defAnim = BOTH_BF1LOCK; + attStart = defStart = 0.5f; + idealDist = LOCK_IDEAL_DIST_TOP; + break; + case LOCK_DIAG_TR: + attAnim = BOTH_CCWCIRCLELOCK; + defAnim = BOTH_CWCIRCLELOCK; + attStart = defStart = 0.5f; + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_DIAG_TL: + attAnim = BOTH_CWCIRCLELOCK; + defAnim = BOTH_CCWCIRCLELOCK; + attStart = defStart = 0.5f; + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_DIAG_BR: + attAnim = BOTH_CWCIRCLELOCK; + defAnim = BOTH_CCWCIRCLELOCK; + attStart = defStart = 0.85f; + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_DIAG_BL: + attAnim = BOTH_CCWCIRCLELOCK; + defAnim = BOTH_CWCIRCLELOCK; + attStart = defStart = 0.85f; + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_R: + attAnim = BOTH_CCWCIRCLELOCK; + defAnim = BOTH_CWCIRCLELOCK; + attStart = defStart = 0.75f; + idealDist = LOCK_IDEAL_DIST_CIRCLE; + break; + case LOCK_L: + attAnim = BOTH_CWCIRCLELOCK; + defAnim = BOTH_CCWCIRCLELOCK; + attStart = defStart = 0.75f; + 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.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_TOP, SABERLOCK_LOCK, SABERLOCK_WIN ); + defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.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.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + attStart = defStart = 0.5f; + break; + case LOCK_DIAG_TL: + attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + attStart = defStart = 0.5f; + break; + case LOCK_DIAG_BR: + attAnim = G_SaberLockAnim( attacker->client->ps.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) ) + { + attStart = 0.85f;//move to end of anim + } + else + { + attStart = 0.15f;//start at beginning of anim + } + if ( BG_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.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) ) + { + attStart = 0.85f;//move to end of anim + } + else + { + attStart = 0.15f;//start at beginning of anim + } + if ( BG_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.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) ) + { + attStart = 0.75f;//move to end of anim + } + else + { + attStart = 0.25f;//start at beginning of anim + } + if ( BG_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.fd.saberAnimLevel, defender->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_WIN ); + defAnim = G_SaberLockAnim( defender->client->ps.fd.saberAnimLevel, attacker->client->ps.fd.saberAnimLevel, SABERLOCK_SIDE, SABERLOCK_LOCK, SABERLOCK_LOSE ); + //attacker starts with advantage + if ( BG_CheckIncrementLockAnim( attAnim, SABERLOCK_WIN ) ) + { + attStart = 0.75f;//move to end of anim + } + else + { + attStart = 0.25f;//start at beginning of anim + } + if ( BG_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; + } + } + } + + G_SetAnim(attacker, NULL, SETANIM_BOTH, attAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + attacker->client->ps.saberLockFrame = bgAllAnims[attacker->localAnimIndex].anims[attAnim].firstFrame+(bgAllAnims[attacker->localAnimIndex].anims[attAnim].numFrames*attStart); + + G_SetAnim(defender, NULL, SETANIM_BOTH, defAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + defender->client->ps.saberLockFrame = bgAllAnims[defender->localAnimIndex].anims[defAnim].firstFrame+(bgAllAnims[defender->localAnimIndex].anims[defAnim].numFrames*defStart); + + attacker->client->ps.saberLockHits = 0; + defender->client->ps.saberLockHits = 0; + + attacker->client->ps.saberLockAdvance = qfalse; + defender->client->ps.saberLockAdvance = qfalse; + + VectorClear( attacker->client->ps.velocity ); + VectorClear( defender->client->ps.velocity ); + attacker->client->ps.saberLockTime = defender->client->ps.saberLockTime = level.time + 10000; + attacker->client->ps.saberLockEnemy = defender->s.number; + defender->client->ps.saberLockEnemy = attacker->s.number; + attacker->client->ps.weaponTime = defender->client->ps.weaponTime = Q_irand( 1000, 3000 );//delay 1 to 3 seconds before pushing + + VectorSubtract( defender->r.currentOrigin, attacker->r.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 + diff = VectorNormalize( defDir ) - idealDist;//diff will be the total error in dist + //try to move attacker half the diff towards the defender + VectorMA( attacker->r.currentOrigin, diff*0.5f, defDir, newOrg ); + + trap_Trace( &trace, attacker->r.currentOrigin, attacker->r.mins, attacker->r.maxs, newOrg, attacker->s.number, attacker->clipmask ); + if ( !trace.startsolid && !trace.allsolid ) + { + G_SetOrigin( attacker, trace.endpos ); + if (attacker->client) + { + VectorCopy(trace.endpos, attacker->client->ps.origin); + } + trap_LinkEntity( attacker ); + } + //now get the defender's dist and do it for him too + VectorSubtract( attacker->r.currentOrigin, defender->r.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->r.currentOrigin, diff, attDir, newOrg ); + trap_Trace( &trace, defender->r.currentOrigin, defender->r.mins, defender->r.maxs, newOrg, defender->s.number, defender->clipmask ); + if ( !trace.startsolid && !trace.allsolid ) + { + if (defender->client) + { + VectorCopy(trace.endpos, defender->client->ps.origin); + } + G_SetOrigin( defender, trace.endpos ); + trap_LinkEntity( defender ); + } + + //DONE! + return qtrue; +} + +qboolean WP_SabersCheckLock( gentity_t *ent1, gentity_t *ent2 ) +{ + float dist; + qboolean ent1BlockingPlayer = qfalse; + qboolean ent2BlockingPlayer = qfalse; + + if ( g_debugSaberLocks.integer ) + { + WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM ); + return qtrue; + } + //for now.. it's not fair to the lone duelist. + //we need dual saber lock animations. + if (g_gametype.integer == GT_POWERDUEL) + { + return qfalse; + } + + if (!g_saberLocking.integer) + { + return qfalse; + } + + if (!ent1->client || !ent2->client) + { + return qfalse; + } + + if (ent1->s.eType == ET_NPC || + ent2->s.eType == ET_NPC) + { //if either ents is NPC, then never let an NPC lock with someone on the same playerTeam + if (ent1->client->playerTeam == ent2->client->playerTeam) + { + return qfalse; + } + } + + if (!ent1->client->ps.saberEntityNum || + !ent2->client->ps.saberEntityNum || + ent1->client->ps.saberInFlight || + ent2->client->ps.saberInFlight) + { //can't get in lock if one of them has had the saber knocked out of his hand + return qfalse; + } + + if (ent1->s.eType != ET_NPC && ent2->s.eType != ET_NPC) + { //can always get into locks with NPCs + if (!ent1->client->ps.duelInProgress || + !ent2->client->ps.duelInProgress || + ent1->client->ps.duelIndex != ent2->s.number || + ent2->client->ps.duelIndex != ent1->s.number) + { //only allow saber locking if two players are dueling with each other directly + if (g_gametype.integer != GT_DUEL && g_gametype.integer != GT_POWERDUEL) + { + return qfalse; + } + } + } + + if ( fabs( ent1->r.currentOrigin[2]-ent2->r.currentOrigin[2] ) > 16 ) + { + return qfalse; + } + if ( ent1->client->ps.groundEntityNum == ENTITYNUM_NONE || + ent2->client->ps.groundEntityNum == ENTITYNUM_NONE ) + { + return qfalse; + } + dist = DistanceSquared(ent1->r.currentOrigin,ent2->r.currentOrigin); + if ( dist < 64 || dist > 6400 ) + {//between 8 and 80 from each other + return qfalse; + } + + if (BG_InSpecialJump(ent1->client->ps.legsAnim)) + { + return qfalse; + } + if (BG_InSpecialJump(ent2->client->ps.legsAnim)) + { + return qfalse; + } + + if (BG_InRoll(&ent1->client->ps, ent1->client->ps.legsAnim)) + { + return qfalse; + } + if (BG_InRoll(&ent2->client->ps, ent2->client->ps.legsAnim)) + { + return qfalse; + } + + if (ent1->client->ps.forceHandExtend != HANDEXTEND_NONE || + ent2->client->ps.forceHandExtend != HANDEXTEND_NONE) + { + return qfalse; + } + + if ((ent1->client->ps.pm_flags & PMF_DUCKED) || + (ent2->client->ps.pm_flags & PMF_DUCKED)) + { + return qfalse; + } + + if ( !ent1->client->saber[0].lockable + || !ent2->client->saber[0].lockable ) + { + return qfalse; + } + if ( ent1->client->saber[1].model + && ent1->client->saber[1].model[0] + && !ent1->client->ps.saberHolstered + && !ent1->client->saber[1].lockable ) + { + return qfalse; + } + if ( ent2->client->saber[1].model + && ent2->client->saber[1].model[0] + && !ent2->client->ps.saberHolstered + && !ent2->client->saber[1].lockable ) + { + return qfalse; + } + + if (!InFront( ent1->client->ps.origin, ent2->client->ps.origin, ent2->client->ps.viewangles, 0.4f )) + { + return qfalse; + } + if (!InFront( ent2->client->ps.origin, ent1->client->ps.origin, ent1->client->ps.viewangles, 0.4f )) + { + return qfalse; + } + + //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 + 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 + return WP_SabersCheckLock2( ent2, ent1, LOCK_TOP ); + } + + if ( ent1->s.number == 0 && + ent1->client->ps.saberBlocking == BLK_WIDE && ent1->client->ps.weaponTime <= 0 ) + { + ent1BlockingPlayer = qtrue; + } + if ( ent2->s.number == 0 && + ent2->client->ps.saberBlocking == BLK_WIDE && ent2->client->ps.weaponTime <= 0 ) + { + ent2BlockingPlayer = qtrue; + } + + //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 + 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 ); + } + return qfalse; + } + + 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 + 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 ); + } + return qfalse; + } + + //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 + 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 ); + } + return qfalse; + } + + 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 + 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 ); + } + return qfalse; + } + //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 + 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 ); + } + return qfalse; + } + 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 + 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 ); + } + return qfalse; + } + //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 + 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 ); + } + return qfalse; + } + 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 + 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 ); + } + return qfalse; + } + if ( !Q_irand( 0, 10 ) ) + { + return WP_SabersCheckLock2( ent1, ent2, LOCK_RANDOM ); + } + return qfalse; +} + +static GAME_INLINE int G_GetParryForBlock(int block) +{ + switch (block) + { + case BLOCKED_UPPER_RIGHT: + return LS_PARRY_UR; + break; + case BLOCKED_UPPER_RIGHT_PROJ: + return LS_REFLECT_UR; + break; + case BLOCKED_UPPER_LEFT: + return LS_PARRY_UL; + break; + case BLOCKED_UPPER_LEFT_PROJ: + return LS_REFLECT_UL; + break; + case BLOCKED_LOWER_RIGHT: + return LS_PARRY_LR; + break; + case BLOCKED_LOWER_RIGHT_PROJ: + return LS_REFLECT_LR; + break; + case BLOCKED_LOWER_LEFT: + return LS_PARRY_LL; + break; + case BLOCKED_LOWER_LEFT_PROJ: + return LS_REFLECT_LL; + break; + case BLOCKED_TOP: + return LS_PARRY_UP; + break; + case BLOCKED_TOP_PROJ: + return LS_REFLECT_UP; + break; + default: + break; + } + + return LS_NONE; +} + +#include "../namespace_begin.h" +int PM_SaberBounceForAttack( int move ); +int PM_SaberDeflectionForQuad( int quad ); +#include "../namespace_end.h" + +extern stringID_table_t animTable[MAX_ANIMATIONS+1]; +static GAME_INLINE qboolean WP_GetSaberDeflectionAngle( gentity_t *attacker, gentity_t *defender, float saberHitFraction ) +{ + qboolean animBasedDeflection = qtrue; + int attSaberLevel, defSaberLevel; + + if ( !attacker || !attacker->client || !attacker->ghoul2 ) + { + return qfalse; + } + if ( !defender || !defender->client || !defender->ghoul2 ) + { + return qfalse; + } + + if ((level.time - attacker->client->lastSaberStorageTime) > 500) + { //last update was too long ago, something is happening to this client to prevent his saber from updating + return qfalse; + } + if ((level.time - defender->client->lastSaberStorageTime) > 500) + { //ditto + return qfalse; + } + + attSaberLevel = G_SaberAttackPower(attacker, SaberAttacking(attacker)); + defSaberLevel = G_SaberAttackPower(defender, SaberAttacking(defender)); + + if ( animBasedDeflection ) + { + //Hmm, let's try just basing it off the anim + int attQuadStart = saberMoveData[attacker->client->ps.saberMove].startQuad; + int attQuadEnd = saberMoveData[attacker->client->ps.saberMove].endQuad; + int defQuad = saberMoveData[defender->client->ps.saberMove].endQuad; + int quadDiff = fabs((float)(defQuad-attQuadStart)); + + if ( defender->client->ps.saberMove == LS_READY ) + { + //FIXME: we should probably do SOMETHING here... + //I have this return qfalse here in the hopes that + //the defender will pick a parry and the attacker + //will hit the defender's saber again. + //But maybe this func call should come *after* + //it's decided whether or not the defender is + //going to parry. + return qfalse; + } + + //reverse the left/right of the defQuad because of the mirrored nature of facing each other in combat + switch ( defQuad ) + { + case Q_BR: + defQuad = Q_BL; + break; + case Q_R: + defQuad = Q_L; + break; + case Q_TR: + defQuad = Q_TL; + break; + case Q_TL: + defQuad = Q_TR; + break; + case Q_L: + defQuad = Q_R; + break; + case Q_BL: + defQuad = Q_BR; + break; + } + + if ( quadDiff > 4 ) + {//wrap around so diff is never greater than 180 (4 * 45) + quadDiff = 4 - (quadDiff - 4); + } + //have the quads, find a good anim to use + if ( (!quadDiff || (quadDiff == 1 && Q_irand(0,1))) //defender pretty much stopped the attack at a 90 degree angle + && (defSaberLevel == attSaberLevel || Q_irand( 0, defSaberLevel-attSaberLevel ) >= 0) )//and the defender's style is stronger + { + //bounce straight back +#ifndef FINAL_BUILD + int attMove = attacker->client->ps.saberMove; +#endif + attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove ); +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf( "attack %s vs. parry %s bounced to %s\n", + animTable[saberMoveData[attMove].animToUse].name, + animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name, + animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name ); + } +#endif + attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + return qfalse; + } + else + {//attack hit at an angle, figure out what angle it should bounce off att + int newQuad; + quadDiff = defQuad - attQuadEnd; + //add half the diff of between the defense and attack end to the attack end + if ( quadDiff > 4 ) + { + quadDiff = 4 - (quadDiff - 4); + } + else if ( quadDiff < -4 ) + { + quadDiff = -4 + (quadDiff + 4); + } + newQuad = attQuadEnd + ceil( ((float)quadDiff)/2.0f ); + if ( newQuad < Q_BR ) + {//less than zero wraps around + newQuad = Q_B + newQuad; + } + if ( newQuad == attQuadStart ) + {//never come off at the same angle that we would have if the attack was not interrupted + if ( Q_irand(0, 1) ) + { + newQuad--; + } + else + { + newQuad++; + } + if ( newQuad < Q_BR ) + { + newQuad = Q_B; + } + else if ( newQuad > Q_B ) + { + newQuad = Q_BR; + } + } + if ( newQuad == defQuad ) + {//bounce straight back +#ifndef FINAL_BUILD + int attMove = attacker->client->ps.saberMove; +#endif + attacker->client->ps.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove ); +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf( "attack %s vs. parry %s bounced to %s\n", + animTable[saberMoveData[attMove].animToUse].name, + animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name, + animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name ); + } +#endif + attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + return qfalse; + } + //else, pick a deflection + else + { +#ifndef FINAL_BUILD + int attMove = attacker->client->ps.saberMove; +#endif + attacker->client->ps.saberMove = PM_SaberDeflectionForQuad( newQuad ); +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf( "attack %s vs. parry %s deflected to %s\n", + animTable[saberMoveData[attMove].animToUse].name, + animTable[saberMoveData[defender->client->ps.saberMove].animToUse].name, + animTable[saberMoveData[attacker->client->ps.saberMove].animToUse].name ); + } +#endif + attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + return qtrue; + } + } + } + else + { //old math-based method (probably broken) + vec3_t att_HitDir, def_BladeDir, temp; + float hitDot; + + VectorCopy(attacker->client->lastSaberBase_Always, temp); + + AngleVectors(attacker->client->lastSaberDir_Always, att_HitDir, 0, 0); + + AngleVectors(defender->client->lastSaberDir_Always, def_BladeDir, 0, 0); + + //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.saberMove = PM_SaberBounceForAttack( attacker->client->ps.saberMove ); + attacker->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + 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.saberMove = LS_D1_TR; + } + else if ( swingUDot < -0.25f ) + {//deflect to bottom + attacker->client->ps.saberMove = LS_D1_BR; + } + else + {//deflect horizontally + attacker->client->ps.saberMove = LS_D1__R; + } + } + else if ( swingRDot < -0.25f ) + {//deflect to left + if ( swingUDot > 0.25f ) + {//deflect to top + attacker->client->ps.saberMove = LS_D1_TL; + } + else if ( swingUDot < -0.25f ) + {//deflect to bottom + attacker->client->ps.saberMove = LS_D1_BL; + } + else + {//deflect horizontally + attacker->client->ps.saberMove = LS_D1__L; + } + } + else + {//deflect in middle + if ( swingUDot > 0.25f ) + {//deflect to top + attacker->client->ps.saberMove = LS_D1_T_; + } + else if ( swingUDot < -0.25f ) + {//deflect to bottom + attacker->client->ps.saberMove = 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.saberMove = LS_D1_TR; + } + else if ( swingRDot < 0 ) + { + attacker->client->ps.saberMove = LS_D1_TL; + } + else + { + attacker->client->ps.saberMove = LS_D1_T_; + } + } + } + + attacker->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + return qtrue; + } + } +} + +int G_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 LS_PARRY_UP: + return LS_K1_T_;//push up + break; + case LS_PARRY_UR: + default://case LS_READY: + return LS_K1_TR;//push up, slightly to right + break; + case LS_PARRY_UL: + return LS_K1_TL;//push up and to left + break; + case LS_PARRY_LR: + return LS_K1_BR;//push down and to left + break; + case LS_PARRY_LL: + return LS_K1_BL;//push down and to right + break; + } +} + +#define SABER_NONATTACK_DAMAGE 1 + +//For strong attacks, we ramp damage based on the point in the attack animation +static GAME_INLINE int G_GetAttackDamage(gentity_t *self, int minDmg, int maxDmg, float multPoint) +{ + int peakDif = 0; + int speedDif = 0; + int totalDamage = maxDmg; + float peakPoint = 0; + float attackAnimLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].numFrames * fabs((float)(bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].frameLerp)); + float currentPoint = 0; + float damageFactor = 0; + float animSpeedFactor = 1.0f; + + //Be sure to scale by the proper anim speed just as if we were going to play the animation + BG_SaberStartTransAnim(self->client->ps.fd.saberAnimLevel, self->client->ps.torsoAnim, &animSpeedFactor, self->client->ps.brokenLimbs); + speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor); + attackAnimLength += speedDif; + peakPoint = attackAnimLength; + peakPoint -= attackAnimLength*multPoint; + + //we treat torsoTimer as the point in the animation (closer it is to attackAnimLength, closer it is to beginning) + currentPoint = self->client->ps.torsoTimer; + + if (peakPoint > currentPoint) + { + peakDif = (peakPoint - currentPoint); + } + else + { + peakDif = (currentPoint - peakPoint); + } + + damageFactor = (float)((currentPoint/peakPoint)); + if (damageFactor > 1) + { + damageFactor = (2.0f - damageFactor); + } + + totalDamage *= damageFactor; + if (totalDamage < minDmg) + { + totalDamage = minDmg; + } + if (totalDamage > maxDmg) + { + totalDamage = maxDmg; + } + + //Com_Printf("%i\n", totalDamage); + + return totalDamage; +} + +//Get the point in the animation and return a percentage of the current point in the anim between 0 and the total anim length (0.0f - 1.0f) +static GAME_INLINE float G_GetAnimPoint(gentity_t *self) +{ + int speedDif = 0; + float attackAnimLength = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].numFrames * fabs((float)(bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].frameLerp)); + float currentPoint = 0; + float animSpeedFactor = 1.0f; + float animPercentage = 0; + + //Be sure to scale by the proper anim speed just as if we were going to play the animation + BG_SaberStartTransAnim(self->client->ps.fd.saberAnimLevel, self->client->ps.torsoAnim, &animSpeedFactor, self->client->ps.brokenLimbs); + speedDif = attackAnimLength - (attackAnimLength * animSpeedFactor); + attackAnimLength += speedDif; + + currentPoint = self->client->ps.torsoTimer; + + animPercentage = currentPoint/attackAnimLength; + + //Com_Printf("%f\n", animPercentage); + + return animPercentage; +} + +static GAME_INLINE qboolean G_ClientIdleInWorld(gentity_t *ent) +{ + if (ent->s.eType == ET_NPC) + { + return qfalse; + } + + if (!ent->client->pers.cmd.upmove && + !ent->client->pers.cmd.forwardmove && + !ent->client->pers.cmd.rightmove && + !(ent->client->pers.cmd.buttons & BUTTON_GESTURE) && + !(ent->client->pers.cmd.buttons & BUTTON_FORCEGRIP) && + !(ent->client->pers.cmd.buttons & BUTTON_ALT_ATTACK) && + !(ent->client->pers.cmd.buttons & BUTTON_FORCEPOWER) && + !(ent->client->pers.cmd.buttons & BUTTON_FORCE_LIGHTNING) && + !(ent->client->pers.cmd.buttons & BUTTON_FORCE_DRAIN) && + !(ent->client->pers.cmd.buttons & BUTTON_ATTACK)) + { + return qtrue; + } + + return qfalse; +} + +static GAME_INLINE qboolean G_G2TraceCollide(trace_t *tr, vec3_t lastValidStart, vec3_t lastValidEnd, vec3_t traceMins, vec3_t traceMaxs) +{ //Hit the ent with the normal trace, try the collision trace. + G2Trace_t G2Trace; + gentity_t *g2Hit; + vec3_t angles; + int tN = 0; + float fRadius = 0; + + if (!d_saberGhoul2Collision.integer) + { + return qfalse; + } + + if (!g_entities[tr->entityNum].inuse /*|| + (g_entities[tr->entityNum].s.eFlags & EF_DEAD)*/) + { //don't do perpoly on corpses. + return qfalse; + } + + if (traceMins[0] || + traceMins[1] || + traceMins[2] || + traceMaxs[0] || + traceMaxs[1] || + traceMaxs[2]) + { + fRadius=(traceMaxs[0]-traceMins[0])/2.0f; + } + + memset (&G2Trace, 0, sizeof(G2Trace)); + + while (tN < MAX_G2_COLLISIONS) + { + G2Trace[tN].mEntityNum = -1; + tN++; + } + g2Hit = &g_entities[tr->entityNum]; + + if (g2Hit && g2Hit->inuse && g2Hit->ghoul2) + { + vec3_t g2HitOrigin; + + angles[ROLL] = angles[PITCH] = 0; + + if (g2Hit->client) + { + VectorCopy(g2Hit->client->ps.origin, g2HitOrigin); + angles[YAW] = g2Hit->client->ps.viewangles[YAW]; + } + else + { + VectorCopy(g2Hit->r.currentOrigin, g2HitOrigin); + angles[YAW] = g2Hit->r.currentAngles[YAW]; + } + + trap_G2API_CollisionDetect ( G2Trace, g2Hit->ghoul2, angles, g2HitOrigin, level.time, g2Hit->s.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, g_g2TraceLod.integer, fRadius ); + + if (G2Trace[0].mEntityNum != g2Hit->s.number) + { + tr->fraction = 1.0f; + tr->entityNum = ENTITYNUM_NONE; + tr->startsolid = 0; + tr->allsolid = 0; + return qfalse; + } + else + { //The ghoul2 trace result matches, so copy the collision position into the trace endpos and send it back. + VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos); + VectorCopy(G2Trace[0].mCollisionNormal, tr->plane.normal); + + if (g2Hit->client) + { + g2Hit->client->g2LastSurfaceHit = G2Trace[0].mSurfaceIndex; + g2Hit->client->g2LastSurfaceTime = level.time; + } + return qtrue; + } + } + + return qfalse; +} + +static GAME_INLINE qboolean G_SaberInBackAttack(int move) +{ + switch (move) + { + case LS_A_BACK: + case LS_A_BACK_CR: + case LS_A_BACKSTAB: + return qtrue; + } + + return qfalse; +} + +qboolean saberCheckKnockdown_Thrown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other); +qboolean saberCheckKnockdown_Smashed(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other, int damage); +qboolean saberCheckKnockdown_BrokenParry(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other); + + +typedef struct saberFace_s +{ + vec3_t v1; + vec3_t v2; + vec3_t v3; +} saberFace_t; + +//build faces around blade for collision checking -rww +static GAME_INLINE void G_BuildSaberFaces(vec3_t base, vec3_t tip, float radius, vec3_t fwd, + vec3_t right, int *fNum, saberFace_t **fList) +{ + static saberFace_t faces[12]; + int i = 0; + float *d1 = NULL, *d2 = NULL; + vec3_t invFwd; + vec3_t invRight; + + VectorCopy(fwd, invFwd); + VectorInverse(invFwd); + VectorCopy(right, invRight); + VectorInverse(invRight); + + while (i < 8) + { + //yeah, this part is kind of a hack, but eh + if (i < 2) + { //"left" surface + d1 = &fwd[0]; + d2 = &invRight[0]; + } + else if (i < 4) + { //"right" surface + d1 = &fwd[0]; + d2 = &right[0]; + } + else if (i < 6) + { //"front" surface + d1 = &right[0]; + d2 = &fwd[0]; + } + else if (i < 8) + { //"back" surface + d1 = &right[0]; + d2 = &invFwd[0]; + } + + //first triangle for this surface + VectorMA(base, radius/2.0f, d1, faces[i].v1); + VectorMA(faces[i].v1, radius/2.0f, d2, faces[i].v1); + + VectorMA(tip, radius/2.0f, d1, faces[i].v2); + VectorMA(faces[i].v2, radius/2.0f, d2, faces[i].v2); + + VectorMA(tip, -radius/2.0f, d1, faces[i].v3); + VectorMA(faces[i].v3, radius/2.0f, d2, faces[i].v3); + + i++; + + //second triangle for this surface + VectorMA(tip, -radius/2.0f, d1, faces[i].v1); + VectorMA(faces[i].v1, radius/2.0f, d2, faces[i].v1); + + VectorMA(base, radius/2.0f, d1, faces[i].v2); + VectorMA(faces[i].v2, radius/2.0f, d2, faces[i].v2); + + VectorMA(base, -radius/2.0f, d1, faces[i].v3); + VectorMA(faces[i].v3, radius/2.0f, d2, faces[i].v3); + + i++; + } + + //top surface + //face 1 + VectorMA(tip, radius/2.0f, fwd, faces[i].v1); + VectorMA(faces[i].v1, -radius/2.0f, right, faces[i].v1); + + VectorMA(tip, radius/2.0f, fwd, faces[i].v2); + VectorMA(faces[i].v2, radius/2.0f, right, faces[i].v2); + + VectorMA(tip, -radius/2.0f, fwd, faces[i].v3); + VectorMA(faces[i].v3, -radius/2.0f, right, faces[i].v3); + + i++; + + //face 2 + VectorMA(tip, radius/2.0f, fwd, faces[i].v1); + VectorMA(faces[i].v1, radius/2.0f, right, faces[i].v1); + + VectorMA(tip, -radius/2.0f, fwd, faces[i].v2); + VectorMA(faces[i].v2, -radius/2.0f, right, faces[i].v2); + + VectorMA(tip, -radius/2.0f, fwd, faces[i].v3); + VectorMA(faces[i].v3, radius/2.0f, right, faces[i].v3); + + i++; + + //bottom surface + //face 1 + VectorMA(base, radius/2.0f, fwd, faces[i].v1); + VectorMA(faces[i].v1, -radius/2.0f, right, faces[i].v1); + + VectorMA(base, radius/2.0f, fwd, faces[i].v2); + VectorMA(faces[i].v2, radius/2.0f, right, faces[i].v2); + + VectorMA(base, -radius/2.0f, fwd, faces[i].v3); + VectorMA(faces[i].v3, -radius/2.0f, right, faces[i].v3); + + i++; + + //face 2 + VectorMA(base, radius/2.0f, fwd, faces[i].v1); + VectorMA(faces[i].v1, radius/2.0f, right, faces[i].v1); + + VectorMA(base, -radius/2.0f, fwd, faces[i].v2); + VectorMA(faces[i].v2, -radius/2.0f, right, faces[i].v2); + + VectorMA(base, -radius/2.0f, fwd, faces[i].v3); + VectorMA(faces[i].v3, radius/2.0f, right, faces[i].v3); + + i++; + + //yeah.. always going to be 12 I suppose. + *fNum = i; + *fList = &faces[0]; +} + +//collision utility function -rww +static GAME_INLINE void G_SabCol_CalcPlaneEq(vec3_t x, vec3_t y, vec3_t z, float *planeEq) +{ + planeEq[0] = x[1]*(y[2]-z[2]) + y[1]*(z[2]-x[2]) + z[1]*(x[2]-y[2]); + planeEq[1] = x[2]*(y[0]-z[0]) + y[2]*(z[0]-x[0]) + z[2]*(x[0]-y[0]); + planeEq[2] = x[0]*(y[1]-z[1]) + y[0]*(z[1]-x[1]) + z[0]*(x[1]-y[1]); + planeEq[3] = -(x[0]*(y[1]*z[2] - z[1]*y[2]) + y[0]*(z[1]*x[2] - x[1]*z[2]) + z[0]*(x[1]*y[2] - y[1]*x[2]) ); +} + +//collision utility function -rww +static GAME_INLINE int G_SabCol_PointRelativeToPlane(vec3_t pos, float *side, float *planeEq) +{ + *side = planeEq[0]*pos[0] + planeEq[1]*pos[1] + planeEq[2]*pos[2] + planeEq[3]; + + if (*side > 0.0f) + { + return 1; + } + else if (*side < 0.0f) + { + return -1; + } + + return 0; +} + +//do actual collision check using generated saber "faces" +static GAME_INLINE qboolean G_SaberFaceCollisionCheck(int fNum, saberFace_t *fList, vec3_t atkStart, + vec3_t atkEnd, vec3_t atkMins, vec3_t atkMaxs, vec3_t impactPoint) +{ + static float planeEq[4]; + static float side, side2, dist; + static vec3_t dir; + static vec3_t point; + int i = 0; + + if (VectorCompare(atkMins, vec3_origin) && VectorCompare(atkMaxs, vec3_origin)) + { + VectorSet(atkMins, -1.0f, -1.0f, -1.0f); + VectorSet(atkMaxs, 1.0f, 1.0f, 1.0f); + } + + VectorSubtract(atkEnd, atkStart, dir); + + while (i < fNum) + { + G_SabCol_CalcPlaneEq(fList->v1, fList->v2, fList->v3, planeEq); + + if (G_SabCol_PointRelativeToPlane(atkStart, &side, planeEq) != + G_SabCol_PointRelativeToPlane(atkEnd, &side2, planeEq)) + { //start/end points intersect with the plane + static vec3_t extruded; + static vec3_t minPoint, maxPoint; + static vec3_t planeNormal; + static int facing; + + VectorCopy(&planeEq[0], planeNormal); + side2 = planeNormal[0]*dir[0] + planeNormal[1]*dir[1] + planeNormal[2]*dir[2]; + + dist = side/side2; + VectorMA(atkStart, -dist, dir, point); + + VectorAdd(point, atkMins, minPoint); + VectorAdd(point, atkMaxs, maxPoint); + + //point is now the point at which we intersect on the plane. + //see if that point is within the edges of the face. + VectorMA(fList->v1, -2.0f, planeNormal, extruded); + G_SabCol_CalcPlaneEq(fList->v1, fList->v2, extruded, planeEq); + facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq); + + if (facing < 0) + { //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane + facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq); + if (facing < 0) + { + facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq); + } + } + + if (facing >= 0) + { //first edge is facing... + VectorMA(fList->v2, -2.0f, planeNormal, extruded); + G_SabCol_CalcPlaneEq(fList->v2, fList->v3, extruded, planeEq); + facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq); + + if (facing < 0) + { //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane + facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq); + if (facing < 0) + { + facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq); + } + } + + if (facing >= 0) + { //second edge is facing... + VectorMA(fList->v3, -2.0f, planeNormal, extruded); + G_SabCol_CalcPlaneEq(fList->v3, fList->v1, extruded, planeEq); + facing = G_SabCol_PointRelativeToPlane(point, &side, planeEq); + + if (facing < 0) + { //not intersecting.. let's try with the mins/maxs and see if they interesect on the edge plane + facing = G_SabCol_PointRelativeToPlane(minPoint, &side, planeEq); + if (facing < 0) + { + facing = G_SabCol_PointRelativeToPlane(maxPoint, &side, planeEq); + } + } + + if (facing >= 0) + { //third edge is facing.. success + VectorCopy(point, impactPoint); + return qtrue; + } + } + } + } + + i++; + fList++; + } + + //did not hit anything + return qfalse; +} + +//check for collision of 2 blades -rww +static GAME_INLINE qboolean G_SaberCollide(gentity_t *atk, gentity_t *def, vec3_t atkStart, + vec3_t atkEnd, vec3_t atkMins, vec3_t atkMaxs, vec3_t impactPoint) +{ + static int i, j; + + if (!g_saberBladeFaces.integer) + { //detailed check not enabled + return qtrue; + } + + if (!atk->inuse || !atk->client || !def->inuse || !def->client) + { //must have 2 clients and a valid saber entity + return qfalse; + } + + i = 0; + while (i < MAX_SABERS) + { + j = 0; + if (def->client->saber[i].model && def->client->saber[i].model[0]) + { //valid saber on the defender + bladeInfo_t *blade; + vec3_t v, fwd, right, base, tip; + int fNum; + saberFace_t *fList; + + //go through each blade on the defender's sabers + while (j < def->client->saber[i].numBlades) + { + blade = &def->client->saber[i].blade[j]; + + if ((level.time-blade->storageTime) < 200) + { //recently updated + //first get base and tip of blade + VectorCopy(blade->muzzlePoint, base); + VectorMA(base, blade->lengthMax, blade->muzzleDir, tip); + + //Now get relative angles between the points + VectorSubtract(tip, base, v); + vectoangles(v, v); + AngleVectors(v, NULL, right, fwd); + + //now build collision faces for this blade + G_BuildSaberFaces(base, tip, blade->radius*3.0f, fwd, right, &fNum, &fList); + if (fNum > 0) + { +#if 0 + if (atk->s.number == 0) + { + int x = 0; + saberFace_t *l = fList; + while (x < fNum) + { + G_TestLine(fList->v1, fList->v2, 0x0000ff, 100); + G_TestLine(fList->v2, fList->v3, 0x0000ff, 100); + G_TestLine(fList->v3, fList->v1, 0x0000ff, 100); + + fList++; + x++; + } + fList = l; + } +#endif + + if (G_SaberFaceCollisionCheck(fNum, fList, atkStart, atkEnd, atkMins, atkMaxs, impactPoint)) + { //collided + return qtrue; + } + } + } + j++; + } + } + i++; + } + + return qfalse; +} + +float WP_SaberBladeLength( saberInfo_t *saber ) +{//return largest length + int i; + float len = 0.0f; + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].lengthMax > len ) + { + len = saber->blade[i].lengthMax; + } + } + return len; +} + +float WP_SaberLength( gentity_t *ent ) +{//return largest length + if ( !ent || !ent->client ) + { + return 0.0f; + } + else + { + int i; + float len, bestLen = 0.0f; + for ( i = 0; i < MAX_SABERS; i++ ) + { + len = WP_SaberBladeLength( &ent->client->saber[i] ); + if ( len > bestLen ) + { + bestLen = len; + } + } + return bestLen; + } +} +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; + } +} +/* +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); +#define SABER_EXTRAPOLATE_DIST 16.0f +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; + + if ( !ent1 || !ent2 ) + { + return qfalse; + } + if ( !ent1->client || !ent2->client ) + { + return qfalse; + } + if ( BG_SabersOff( &ent1->client->ps ) + || BG_SabersOff( &ent2->client->ps ) ) + { + return qfalse; + } + + for ( ent2SaberNum = 0; ent2SaberNum < MAX_SABERS; ent2SaberNum++ ) + { + if ( ent2->client->saber[ent2SaberNum].type != SABER_NONE ) + { + for ( ent2BladeNum = 0; ent2BladeNum < ent2->client->saber[ent2SaberNum].numBlades; ent2BladeNum++ ) + { + if ( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax > 0 ) + {//valid saber and this blade is on + //if ( ent1->client->saberInFlight ) + { + VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, saberBase1 ); + VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBaseNext1 ); + + VectorSubtract( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointOld, dir ); + VectorNormalize( dir ); + VectorMA( saberBaseNext1, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext1 ); + + VectorMA( saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirOld, saberTip1 ); + VectorMA( saberBaseNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTipNext1 ); + + VectorSubtract( saberTipNext1, saberTip1, dir ); + VectorNormalize( dir ); + VectorMA( saberTipNext1, SABER_EXTRAPOLATE_DIST, dir, saberTipNext1 ); + } + /* + else + { + VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePoint, saberBase1 ); + VectorCopy( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzlePointNext, saberBaseNext1 ); + VectorMA( saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, saberTip1 ); + VectorMA( saberBaseNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].lengthMax, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDirNext, saberTipNext1 ); + } + */ + + //if ( ent2->client->saberInFlight ) + { + VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, saberBase2 ); + VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBaseNext2 ); + + VectorSubtract( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointOld, dir ); + VectorNormalize( dir ); + VectorMA( saberBaseNext2, SABER_EXTRAPOLATE_DIST, dir, saberBaseNext2 ); + + VectorMA( saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDirOld, saberTip2 ); + VectorMA( saberBaseNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax+SABER_EXTRAPOLATE_DIST, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTipNext2 ); + + VectorSubtract( saberTipNext2, saberTip2, dir ); + VectorNormalize( dir ); + VectorMA( saberTipNext2, SABER_EXTRAPOLATE_DIST, dir, saberTipNext2 ); + } + /* + else + { + VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePoint, saberBase2 ); + VectorCopy( ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzlePointNext, saberBaseNext2 ); + VectorMA( saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir, saberTip2 ); + VectorMA( saberBaseNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].lengthMax, ent2->client->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; + float dot = 0.0f; + + 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 + dot = DotProduct( ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].muzzleDir, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].muzzleDir ); + if ( dot > 0.9f || dot < -0.9f ) + {//too parallel to really block effectively? + continue; + } + } + +#ifdef DEBUG_SABER_BOX + if ( g_saberDebugBox.integer == 2 || g_saberDebugBox.integer == 4 ) + { + G_TestLine(saberBase1, saberTip1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500); + G_TestLine(saberTip1, saberTipNext1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500); + G_TestLine(saberTipNext1, saberBase1, ent1->client->saber[ent1SaberNum].blade[ent1BladeNum].color, 500); + + G_TestLine(saberBase2, saberTip2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500); + G_TestLine(saberTip2, saberTipNext2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500); + G_TestLine(saberTipNext2, saberBase2, ent2->client->saber[ent2SaberNum].blade[ent2BladeNum].color, 500); + } +#endif + 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; +} + +static GAME_INLINE int G_PowerLevelForSaberAnim( gentity_t *ent, int saberNum, qboolean mySaberHit ) +{ + if ( !ent || !ent->client || saberNum >= MAX_SABERS ) + { + return FORCE_LEVEL_0; + } + else + { + int anim = ent->client->ps.torsoAnim; + int animTimer = ent->client->ps.torsoTimer; + int animTimeElapsed = BG_AnimLength( ent->localAnimIndex, (animNumber_t)anim ) - animTimer; + saberInfo_t *saber = &ent->client->saber[saberNum]; + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_D1_B____ ) + { + //FIXME: these two need their own style + if ( saber->type == SABER_LANCE ) + { + return FORCE_LEVEL_4; + } + else if ( saber->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_H1_S1_BR ) + {//parries, knockaways and broken parries + return FORCE_LEVEL_1;//FIXME: saberAnimLevel? + } + switch ( anim ) + { + case BOTH_A2_STABBACK1: + if ( mySaberHit ) + {//someone else hit my saber, not asking for damage level, but defense strength + return FORCE_LEVEL_1; + } + if ( animTimer < 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 ( animTimer < 500 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_CROUCHATTACKBACK1: + if ( animTimer < 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 ( mySaberHit ) + {//someone else hit my saber, not asking for damage level, but defense strength + return FORCE_LEVEL_1; + } + if ( animTimer < 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 ( animTimer < 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 ( animTimer <= 1000 ) + {//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_JUMPFLIPSTABDOWN: + if ( animTimer <= 1300 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed <= 300 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_JUMPATTACK6: + /* + if (pm->ps) + { + if ( ( pm->ps->legsAnimTimer >= 1450 + && BG_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 400 ) + ||(pm->ps->legsAnimTimer >= 400 + && BG_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, BOTH_JUMPATTACK6 ) - pm->ps->legsAnimTimer >= 1100 ) ) + {//pretty much sideways + return FORCE_LEVEL_3; + } + } + */ + if ( ( animTimer >= 1450 + && animTimeElapsed >= 400 ) + ||(animTimer >= 400 + && animTimeElapsed >= 1100 ) ) + {//pretty much sideways + return FORCE_LEVEL_3; + } + return FORCE_LEVEL_0; + break; + case BOTH_JUMPATTACK7: + if ( animTimer <= 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_2;//FORCE_LEVEL_3; + break; + case BOTH_SPINATTACK7: + if ( animTimer <= 500 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 500 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_2;//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 ( animTimer <= 900 ) + {//end of anim + return FORCE_LEVEL_3; + } + break; + case BOTH_STABDOWN_STAFF: + if ( animTimer <= 850 ) + {//end of anim + return FORCE_LEVEL_3; + } + break; + case BOTH_STABDOWN_DUAL: + if ( animTimer <= 900 ) + {//end of anim + return FORCE_LEVEL_3; + } + break; + case BOTH_A6_SABERPROTECT: + if ( animTimer < 650 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 250 ) + {//start of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_A7_SOULCAL: + if ( animTimer < 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 ( animTimer < 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 ( animTimer < 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 ( animTimer < 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 ( mySaberHit ) + {//someone else hit my saber, not asking for damage level, but defense strength + return FORCE_LEVEL_1; + } + if ( animTimer < 1000 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_PULL_IMPALE_SWING: + if ( animTimer < 500 ) + {//end of anim + return FORCE_LEVEL_0; + } + else if ( animTimeElapsed < 650 ) + {//beginning of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_3; + break; + case BOTH_ALORA_SPIN_SLASH: + if ( animTimer < 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 ( mySaberHit ) + {//someone else hit my saber, not asking for damage level, but defense strength + return FORCE_LEVEL_1; + } + if ( animTimer < 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 ( mySaberHit ) + {//someone else hit my saber, not asking for damage level, but defense strength + return FORCE_LEVEL_1; + } + if ( animTimer < 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 ( animTimer < 700 ) + {//end of anim + return FORCE_LEVEL_0; + } + return FORCE_LEVEL_5; + break; + case BOTH_LK_S_ST_S_SB_1_W: + if ( animTimer < 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 ( animTimer < 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 ( animTimer < 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 ( animTimer < 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 ( animTimer < 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 ( animTimer < 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 ( animTimer < 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 ( animTimer < 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 ( mySaberHit ) + {//someone else hit my saber, not asking for damage level, but defense strength + return FORCE_LEVEL_1; + } + if ( animTimeElapsed > 400 ) + {//end of anim + return FORCE_LEVEL_0; + } + else + { + return FORCE_LEVEL_3; + } + break; + } + return FORCE_LEVEL_0; + } +} + +#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 int numVictims = 0; +void WP_SaberClearDamage( void ) +{ + int ven; + for ( 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 ) ); + numVictims = 0; +} + +void WP_SaberDamageAdd( int trVictimEntityNum, vec3_t trDmgDir, vec3_t trDmgSpot, int trDmg ) +{ + if ( trVictimEntityNum < 0 || trVictimEntityNum >= ENTITYNUM_WORLD ) + { + return; + } + + if ( trDmg ) + {//did some damage to something + int curVictim = 0; + int i; + + for ( 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; + } + + totalDmg[curVictim] += trDmg; + if ( VectorCompare( dmgDir[curVictim], vec3_origin ) ) + { + VectorCopy( trDmgDir, dmgDir[curVictim] ); + } + if ( VectorCompare( dmgSpot[curVictim], vec3_origin ) ) + { + VectorCopy( trDmgSpot, dmgSpot[curVictim] ); + } + } +} + +void WP_SaberApplyDamage( gentity_t *self ) +{ + int i; + if ( !numVictims ) + { + return; + } + for ( i = 0; i < numVictims; i++ ) + { + gentity_t *te = NULL, *victim = NULL; + qboolean isDroid = qfalse; + + victim = &g_entities[victimEntityNum[i]]; + +// nmckenzie: SABER_DAMAGE_WALLS + if ( !victim->client ) + { + totalDmg[i] *= g_saberWallDamageScale.value; + } + + G_Damage( victim, self, self, dmgDir[i], dmgSpot[i], totalDmg[i], 0, MOD_SABER ); + + if ( victim->client ) + { + class_t npc_class = victim->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 ) + { //don't make "blood" sparks for droids. + isDroid = qtrue; + } + } + + te = G_TempEntity( dmgSpot[i], EV_SABER_HIT ); + if ( te ) + { + te->s.otherEntityNum = victimEntityNum[i]; + te->s.otherEntityNum2 = self->s.number; + + VectorCopy(dmgSpot[i], te->s.origin); + //VectorCopy(tr.plane.normal, te->s.angles); + VectorScale( dmgDir[i], -1, te->s.angles); + + if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2]) + { //don't let it play with no direction + te->s.angles[1] = 1; + } + + if (!isDroid && (victim->client || victim->s.eType == ET_NPC || + victim->s.eType == ET_BODY)) + { + if ( totalDmg[i] < 5 ) + { + te->s.eventParm = 3; + } + else if (totalDmg[i] < 20 ) + { + te->s.eventParm = 2; + } + else + { + te->s.eventParm = 1; + } + } + else + { + if (totalDmg[i] > SABER_NONATTACK_DAMAGE) + { //I suppose I could tie this into the saberblock event, but I'm tired of adding flags to that thing. + gentity_t *teS = G_TempEntity( te->s.origin, EV_SABER_CLASHFLARE ); + VectorCopy(te->s.origin, teS->s.origin); + } + te->s.eventParm = 0; + } + } + } +} + +static qboolean saberDoClashEffect = qfalse; +static vec3_t saberClashPos = {0}; +static vec3_t saberClashNorm = {0}; +static int saberClashEventParm = 1; +void WP_SaberDoClash( void ) +{ + if ( saberDoClashEffect ) + { + gentity_t *te = G_TempEntity( saberClashPos, EV_SABER_BLOCK ); + VectorCopy(saberClashPos, te->s.origin); + VectorCopy(saberClashNorm, te->s.angles); + te->s.eventParm = saberClashEventParm; + } +} + +static qboolean saberHitWall = qfalse; +static qboolean saberHitSaber = qfalse; +static float saberHitFraction = 1.0f; +//rww - MP version of the saber damage function. This is where all the things like blocking, triggering a parry, +//triggering a broken parry, doing actual damage, etc. are done for the saber. It doesn't resemble the SP +//version very much, but functionality is (hopefully) about the same. +//This is a large function. I feel sort of bad inlining it. But it does get called tons of times per frame. +#include "../namespace_begin.h" +qboolean BG_SuperBreakWinAnim( int anim ); +#include "../namespace_end.h" + +static GAME_INLINE qboolean CheckSaberDamage(gentity_t *self, int rSaberNum, int rBladeNum, vec3_t saberStart, vec3_t saberEnd, qboolean doInterpolate, int trMask, qboolean extrapolate ) +{ + static trace_t tr; + static vec3_t dir; + static vec3_t saberTrMins, saberTrMaxs; + static vec3_t lastValidStart; + static vec3_t lastValidEnd; + static int selfSaberLevel; + static int otherSaberLevel; + int dmg = 0; + int attackStr = 0; + float saberBoxSize = d_saberBoxTraceSize.value; + qboolean idleDamage = qfalse; + qboolean didHit = qfalse; + qboolean sabersClashed = qfalse; + qboolean unblockable = qfalse; + qboolean didDefense = qfalse; + qboolean didOffense = qfalse; + qboolean saberTraceDone = qfalse; + qboolean otherUnblockable = qfalse; + qboolean tryDeflectAgain = qfalse; + + gentity_t *otherOwner; + + if (BG_SabersOff( &self->client->ps )) + { + return qfalse; + } + + selfSaberLevel = G_SaberAttackPower(self, SaberAttacking(self)); + + //Add the standard radius into the box size + saberBoxSize += (self->client->saber[rSaberNum].blade[rBladeNum].radius*0.5f); + + if (self->client->ps.weaponTime <= 0) + { //if not doing any attacks or anything, just use point traces. + VectorClear(saberTrMins); + VectorClear(saberTrMaxs); + } + else if (d_saberGhoul2Collision.integer) + { + if ( d_saberSPStyleDamage.integer ) + {//SP-size saber damage traces + VectorSet(saberTrMins, -2, -2, -2 ); + VectorSet(saberTrMaxs, 2, 2, 2 ); + } + else + { + VectorSet(saberTrMins, -saberBoxSize*3, -saberBoxSize*3, -saberBoxSize*3); + VectorSet(saberTrMaxs, saberBoxSize*3, saberBoxSize*3, saberBoxSize*3); + } + } + else if (self->client->ps.fd.saberAnimLevel < FORCE_LEVEL_2) + { //box trace for fast, because it doesn't get updated so often + VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize); + VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize); + } + else if (d_saberAlwaysBoxTrace.integer) + { + VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize); + VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize); + } + else + { //just trace the minimum blade radius + saberBoxSize = (self->client->saber[rSaberNum].blade[rBladeNum].radius*0.4f); + + VectorSet(saberTrMins, -saberBoxSize, -saberBoxSize, -saberBoxSize); + VectorSet(saberTrMaxs, saberBoxSize, saberBoxSize, saberBoxSize); + } + + while (!saberTraceDone) + { + if ( doInterpolate + && !d_saberSPStyleDamage.integer ) + { //This didn't quite work out like I hoped. But it's better than nothing. Sort of. + vec3_t oldSaberStart, oldSaberEnd, saberDif, oldSaberDif; + int traceTests = 0; + float trDif = 8; + + VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart); + VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, oldSaberEnd); + + VectorSubtract(saberStart, saberEnd, saberDif); + VectorSubtract(oldSaberStart, oldSaberEnd, oldSaberDif); + + VectorNormalize(saberDif); + VectorNormalize(oldSaberDif); + + saberEnd[0] = saberStart[0] - (saberDif[0]*trDif); + saberEnd[1] = saberStart[1] - (saberDif[1]*trDif); + saberEnd[2] = saberStart[2] - (saberDif[2]*trDif); + + oldSaberEnd[0] = oldSaberStart[0] - (oldSaberDif[0]*trDif); + oldSaberEnd[1] = oldSaberStart[1] - (oldSaberDif[1]*trDif); + oldSaberEnd[2] = oldSaberStart[2] - (oldSaberDif[2]*trDif); + + trap_Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask); + + VectorCopy(saberEnd, lastValidStart); + VectorCopy(saberStart, lastValidEnd); + if (tr.entityNum < MAX_CLIENTS) + { + G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs); + } + else if (tr.entityNum < ENTITYNUM_WORLD) + { + gentity_t *trHit = &g_entities[tr.entityNum]; + + if (trHit->inuse && trHit->ghoul2) + { //hit a non-client entity with a g2 instance + G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs); + } + } + + trDif++; + + while (tr.fraction == 1.0 && traceTests < 4 && tr.entityNum >= ENTITYNUM_NONE) + { + VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart); + VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, oldSaberEnd); + + VectorSubtract(saberStart, saberEnd, saberDif); + VectorSubtract(oldSaberStart, oldSaberEnd, oldSaberDif); + + VectorNormalize(saberDif); + VectorNormalize(oldSaberDif); + + saberEnd[0] = saberStart[0] - (saberDif[0]*trDif); + saberEnd[1] = saberStart[1] - (saberDif[1]*trDif); + saberEnd[2] = saberStart[2] - (saberDif[2]*trDif); + + oldSaberEnd[0] = oldSaberStart[0] - (oldSaberDif[0]*trDif); + oldSaberEnd[1] = oldSaberStart[1] - (oldSaberDif[1]*trDif); + oldSaberEnd[2] = oldSaberStart[2] - (oldSaberDif[2]*trDif); + + trap_Trace(&tr, saberEnd, saberTrMins, saberTrMaxs, saberStart, self->s.number, trMask); + + VectorCopy(saberEnd, lastValidStart); + VectorCopy(saberStart, lastValidEnd); + if (tr.entityNum < MAX_CLIENTS) + { + G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs); + } + else if (tr.entityNum < ENTITYNUM_WORLD) + { + gentity_t *trHit = &g_entities[tr.entityNum]; + + if (trHit->inuse && trHit->ghoul2) + { //hit a non-client entity with a g2 instance + G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs); + } + } + + traceTests++; + trDif += 8; + } + } + else + { + vec3_t saberEndExtrapolated; + if ( extrapolate ) + {//extrapolate 16 + vec3_t diff; + VectorSubtract( saberEnd, saberStart, diff ); + VectorNormalize( diff ); + VectorMA( saberStart, SABER_EXTRAPOLATE_DIST, diff, saberEndExtrapolated ); + } + else + { + VectorCopy( saberEnd, saberEndExtrapolated ); + } + trap_Trace(&tr, saberStart, saberTrMins, saberTrMaxs, saberEndExtrapolated, self->s.number, trMask); + + VectorCopy(saberStart, lastValidStart); + VectorCopy(saberEndExtrapolated, lastValidEnd); + /* + if ( tr.allsolid || tr.startsolid ) + { + if ( tr.entityNum == ENTITYNUM_NONE ) + { + qboolean whah = qtrue; + } + Com_Printf( "saber trace start/all solid - ent is %d\n", tr.entityNum ); + } + */ + if (tr.entityNum < MAX_CLIENTS) + { + G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs); + } + else if (tr.entityNum < ENTITYNUM_WORLD) + { + gentity_t *trHit = &g_entities[tr.entityNum]; + + if (trHit->inuse && trHit->ghoul2) + { //hit a non-client entity with a g2 instance + G_G2TraceCollide(&tr, lastValidStart, lastValidEnd, saberTrMins, saberTrMaxs); + } + } + } + + saberTraceDone = qtrue; + } + + if ( (SaberAttacking(self) + || BG_SuperBreakWinAnim(self->client->ps.torsoAnim) + || (d_saberSPStyleDamage.integer&&self->client->ps.saberInFlight&&rSaberNum==0) + || (self->client->ps.m_iVehicleNum && self->client->ps.saberMove > LS_READY) ) && + self->client->ps.saberAttackWound < level.time ) + { //this animation is that of the last attack movement, and so it should do full damage + qboolean saberInSpecial = BG_SaberInSpecial(self->client->ps.saberMove); + qboolean inBackAttack = G_SaberInBackAttack(self->client->ps.saberMove); + + if ( d_saberSPStyleDamage.integer ) + { + float fDmg = 0.0f; + if ( self->client->ps.saberInFlight ) + { + gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum]; + if ( !saberEnt + || !saberEnt->s.saberInFlight ) + {//does less damage on the way back + fDmg = 1.0f; + attackStr = FORCE_LEVEL_0; + } + else + { + fDmg = 2.5f*self->client->ps.fd.forcePowerLevel[FP_SABERTHROW]; + attackStr = FORCE_LEVEL_1; + } + } + else + { + attackStr = G_PowerLevelForSaberAnim( self, rSaberNum, qfalse ); + if ( g_saberRealisticCombat.integer ) + { + switch ( attackStr ) + { + default: + case FORCE_LEVEL_3: + fDmg = 10.0f; + break; + case FORCE_LEVEL_2: + fDmg = 5.0f; + break; + case FORCE_LEVEL_1: + case FORCE_LEVEL_0: + fDmg = 2.5f; + break; + } + } + else + { + if ( self->client->ps.torsoAnim == BOTH_SPINATTACK6 + || self->client->ps.torsoAnim == BOTH_SPINATTACK7 ) + {//too easy to do, lower damage + fDmg = 2.5f; + } + else + { + fDmg = 2.5f * (float)attackStr; + } + } + } + if ( g_saberRealisticCombat.integer > 1 ) + {//always do damage, and lots of it + if ( g_saberRealisticCombat.integer > 2 ) + {//always do damage, and lots of it + fDmg = 25.0f; + } + else if ( fDmg > 0.1f ) + {//only do super damage if we would have done damage according to normal rules + fDmg = 25.0f; + } + } + /* + if ( dmg > 0.1f ) + { + if ( (self->client->ps.forcePowersActive&(1<client->ps.forcePowerLevel[FP_RAGE] * 5.0f; + } + else if ( self->client->ps.forceRageRecoveryTime ) + {//halve it if recovering + dmg *= 0.5f; + } + } + */ + if ( g_gametype.integer != GT_DUEL + && g_gametype.integer != GT_POWERDUEL + && g_gametype.integer != GT_SIEGE ) + {//in faster-paced games, sabers do more damage + fDmg *= 2.0f; + } + if ( fDmg ) + {//the longer the trace, the more damage it does + //FIXME: in SP, we only use the part of the trace that's actually *inside* the hit ent... + float traceLength = Distance( saberEnd, saberStart ); + if ( tr.fraction >= 1.0f ) + {//allsolid? + dmg = ceil( fDmg*traceLength*0.1f*0.33f ); + } + else + {//fractional hit, the sooner you hit in the trace, the more damage you did + dmg = ceil( fDmg*traceLength*(1.0f-tr.fraction)*0.1f*0.33f );//(1.0f-tr.fraction) isn't really accurate, but kind of simulates what we have in SP + } +#ifdef DEBUG_SABER_BOX + if ( g_saberDebugBox.integer == 3 || g_saberDebugBox.integer == 4 ) + { + G_TestLine( saberStart, saberEnd, 0x0000ff, 50 ); + } +#endif + } + /* + if ( dmg ) + { + Com_Printf("CL %i SABER DMG: %i, anim %s, torsoTimer %i\n", self->s.number, dmg, animTable[self->client->ps.torsoAnim].name, self->client->ps.torsoTimer ); + } + */ + if ( self->client->ps.torsoAnim == BOTH_A1_SPECIAL + || self->client->ps.torsoAnim == BOTH_A2_SPECIAL + || self->client->ps.torsoAnim == BOTH_A3_SPECIAL ) + {//parry/block/break-parry bonus for single-style kata moves + attackStr++; + } + if ( BG_SuperBreakWinAnim( self->client->ps.torsoAnim ) ) + { + trMask &= ~CONTENTS_LIGHTSABER; + } + } + else + { + dmg = SABER_HITDAMAGE; + + if (self->client->ps.fd.saberAnimLevel == SS_STAFF || + self->client->ps.fd.saberAnimLevel == SS_DUAL) + { + if (saberInSpecial) + { + //it will get auto-ramped based on the point in the attack, later on + if (self->client->ps.saberMove == LS_SPINATTACK || + self->client->ps.saberMove == LS_SPINATTACK_DUAL) + { //these attacks are long and have the potential to hit a lot so they will do less damage. + dmg = 10; + } + else + { + if ( BG_KickingAnim( self->client->ps.legsAnim ) || + BG_KickingAnim( self->client->ps.torsoAnim ) ) + { //saber shouldn't do more than min dmg during kicks + dmg = 2; + } + else if (BG_SaberInKata(self->client->ps.saberMove)) + { //special kata move + if (self->client->ps.fd.saberAnimLevel == SS_DUAL) + { //this is the nasty saber twirl, do big damage cause it makes you vulnerable + dmg = 90; + } + else + { //staff kata + dmg = G_GetAttackDamage(self, 60, 70, 0.5f); + } + } + else + { + //dmg = 90; + //ramp from 2 to 90 by default for other specials + dmg = G_GetAttackDamage(self, 2, 90, 0.5f); + } + } + } + else + { //otherwise we'll ramp up to 70 I guess, for both dual and staff + dmg = G_GetAttackDamage(self, 2, 70, 0.5f); + } + } + else if (self->client->ps.fd.saberAnimLevel == 3) + { + //new damage-ramping system + if (!saberInSpecial && !inBackAttack) + { + dmg = G_GetAttackDamage(self, 2, 120, 0.5f); + } + else if (saberInSpecial && + (self->client->ps.saberMove == LS_A_JUMP_T__B_)) + { + dmg = G_GetAttackDamage(self, 2, 180, 0.65f); + } + else if (inBackAttack) + { + dmg = G_GetAttackDamage(self, 2, 30, 0.5f); //can hit multiple times (and almost always does), so.. + } + else + { + dmg = 100; + } + } + else if (self->client->ps.fd.saberAnimLevel == 2) + { + if (saberInSpecial && + (self->client->ps.saberMove == LS_A_FLIP_STAB || self->client->ps.saberMove == LS_A_FLIP_SLASH)) + { //a well-timed hit with this can do a full 85 + dmg = G_GetAttackDamage(self, 2, 80, 0.5f); + } + else if (inBackAttack) + { + dmg = G_GetAttackDamage(self, 2, 25, 0.5f); + } + else + { + dmg = 60; + } + } + else if (self->client->ps.fd.saberAnimLevel == 1) + { + if (saberInSpecial && + (self->client->ps.saberMove == LS_A_LUNGE)) + { + dmg = G_GetAttackDamage(self, 2, SABER_HITDAMAGE-5, 0.3f); + } + else if (inBackAttack) + { + dmg = G_GetAttackDamage(self, 2, 30, 0.5f); + } + else + { + dmg = SABER_HITDAMAGE; + } + } + + attackStr = self->client->ps.fd.saberAnimLevel; + } + } + else if (self->client->ps.saberAttackWound < level.time && + self->client->ps.saberIdleWound < level.time) + { //just touching, do minimal damage and only check for it every 200ms (mainly to cut down on network traffic for hit events) + trMask &= ~CONTENTS_LIGHTSABER; + if ( d_saberSPStyleDamage.integer ) + { + if ( BG_SaberInReturn( self->client->ps.saberMove ) ) + { + dmg = SABER_NONATTACK_DAMAGE; + } + else + { + if (d_saberSPStyleDamage.integer == 2) + { + dmg = SABER_NONATTACK_DAMAGE; + } + else + { + dmg = 0; + } + } + } + else + { + dmg = SABER_NONATTACK_DAMAGE; + } + idleDamage = qtrue; + } + else + { + return qtrue; //true cause even though we didn't get a hit, we don't want to do those extra traces because the debounce time says not to. + } + + if (BG_SaberInSpecial(self->client->ps.saberMove)) + { + qboolean inBackAttack = G_SaberInBackAttack(self->client->ps.saberMove); + + unblockable = qtrue; + self->client->ps.saberBlocked = 0; + + if ( d_saberSPStyleDamage.integer ) + { + } + else if (!inBackAttack) + { + if (self->client->ps.saberMove == LS_A_JUMP_T__B_) + { //do extra damage for special unblockables + dmg += 5; //This is very tiny, because this move has a huge damage ramp + } + else if (self->client->ps.saberMove == LS_A_FLIP_STAB || self->client->ps.saberMove == LS_A_FLIP_SLASH) + { + dmg += 5; //ditto + if (dmg <= 40 || G_GetAnimPoint(self) <= 0.4f) + { //sort of a hack, don't want it doing big damage in the off points of the anim + dmg = 2; + } + } + else if (self->client->ps.saberMove == LS_A_LUNGE) + { + dmg += 2; //and ditto again + if (G_GetAnimPoint(self) <= 0.4f) + { //same as above + dmg = 2; + } + } + else if (self->client->ps.saberMove == LS_SPINATTACK || + self->client->ps.saberMove == LS_SPINATTACK_DUAL) + { //do a constant significant amount of damage but ramp up a little to the mid-point + dmg = G_GetAttackDamage(self, 2, dmg+3, 0.5f); + dmg += 10; + } + else + { + //dmg += 20; + if ( BG_KickingAnim( self->client->ps.legsAnim ) || + BG_KickingAnim( self->client->ps.torsoAnim ) ) + { //saber shouldn't do more than min dmg during kicks + dmg = 2; + } + else + { //auto-ramp it I guess since it's a special we don't have a special case for. + dmg = G_GetAttackDamage(self, 5, dmg+5, 0.5f); + } + } + } + } + + if (!dmg) + { + if (tr.entityNum < MAX_CLIENTS || + (g_entities[tr.entityNum].inuse && (g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER))) + { + return qtrue; + } + return qfalse; + } + + if (dmg > SABER_NONATTACK_DAMAGE) + { + dmg *= g_saberDamageScale.value; + + if ((self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)) || + (self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM))) + { //weaken it if an arm is broken + dmg *= 0.3; + if (dmg <= SABER_NONATTACK_DAMAGE) + { + dmg = SABER_NONATTACK_DAMAGE+1; + } + } + } + + if (dmg > SABER_NONATTACK_DAMAGE && self->client->ps.isJediMaster) + { //give the Jedi Master more saber attack power + dmg *= 2; + } + + if (dmg > SABER_NONATTACK_DAMAGE && g_gametype.integer == GT_SIEGE && + self->client->siegeClass != -1 && (bgSiegeClasses[self->client->siegeClass].classflags & (1<client->sess.duelTeam == DUELTEAM_LONE) + { //always x2 when we're powerdueling alone... er, so, we apparently no longer want this? So they say. + if ( g_duel_fraglimit.integer ) + { + //dmg *= 1.5 - (.4 * (float)self->client->sess.wins / (float)g_duel_fraglimit.integer); + + } + //dmg *= 2; + } + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer > 2 && dmg > 1) + { + Com_Printf("CL %i SABER DMG: %i\n", self->s.number, dmg); + } +#endif + + VectorSubtract(saberEnd, saberStart, dir); + VectorNormalize(dir); + + if (tr.entityNum == ENTITYNUM_WORLD || + g_entities[tr.entityNum].s.eType == ET_TERRAIN) + { //register this as a wall hit for jedi AI + self->client->ps.saberEventFlags |= SEF_HITWALL; + saberHitWall = qtrue; + } + + //rww - I'm saying || tr.startsolid here, because otherwise your saber tends to skip positions and go through + //people, and the compensation traces start in their bbox too. Which results in the saber passing through people + //when you visually cut right through them. Which sucks. + + if ((tr.fraction != 1 || tr.startsolid) && + g_entities[tr.entityNum].takedamage && + (g_entities[tr.entityNum].health > 0 || !(g_entities[tr.entityNum].s.eFlags & EF_DISINTEGRATION)) && + tr.entityNum != self->s.number && + g_entities[tr.entityNum].inuse) + {//hit something that had health and takes damage + if (idleDamage && + g_entities[tr.entityNum].client && + OnSameTeam(self, &g_entities[tr.entityNum]) && + !g_friendlySaber.integer) + { + return qfalse; + } + + if (g_entities[tr.entityNum].client && + g_entities[tr.entityNum].client->ps.duelInProgress && + g_entities[tr.entityNum].client->ps.duelIndex != self->s.number) + { + return qfalse; + } + + if (g_entities[tr.entityNum].client && + self->client->ps.duelInProgress && + self->client->ps.duelIndex != g_entities[tr.entityNum].s.number) + { + return qfalse; + } + + if ( BG_StabDownAnim( self->client->ps.torsoAnim ) + && g_entities[tr.entityNum].client + && !BG_InKnockDownOnGround( &g_entities[tr.entityNum].client->ps ) ) + {//stabdowns only damage people who are actually on the ground... + return qfalse; + } + self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer; + + didHit = qtrue; + + if ( !d_saberSPStyleDamage.integer//let's trying making blocks have to be blocked by a saber + && g_entities[tr.entityNum].client + && !unblockable + && WP_SaberCanBlock(&g_entities[tr.entityNum], tr.endpos, 0, MOD_SABER, qfalse, attackStr)) + {//hit a client who blocked the attack (fake: didn't actually hit their saber) + if (dmg <= SABER_NONATTACK_DAMAGE) + { + self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer; + } + saberDoClashEffect = qtrue; + VectorCopy( tr.endpos, saberClashPos ); + VectorCopy( tr.endpos, saberClashNorm ); + saberClashEventParm = 1; + + if (dmg > SABER_NONATTACK_DAMAGE) + { + int lockFactor = g_saberLockFactor.integer; + + if ((g_entities[tr.entityNum].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] - self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) > 1 && + Q_irand(1, 10) < lockFactor*2) + { //Just got blocked by someone with a decently higher attack level, so enter into a lock (where they have the advantage due to a higher attack lev) + if (!G_ClientIdleInWorld(&g_entities[tr.entityNum])) + { + if ( (trMask&CONTENTS_LIGHTSABER) + && WP_SabersCheckLock(self, &g_entities[tr.entityNum])) + { + self->client->ps.saberBlocked = BLOCKED_NONE; + g_entities[tr.entityNum].client->ps.saberBlocked = BLOCKED_NONE; + return didHit; + } + } + } + else if (Q_irand(1, 20) < lockFactor) + { + if (!G_ClientIdleInWorld(&g_entities[tr.entityNum])) + { + if ((trMask&CONTENTS_LIGHTSABER) + && WP_SabersCheckLock(self, &g_entities[tr.entityNum])) + { + self->client->ps.saberBlocked = BLOCKED_NONE; + g_entities[tr.entityNum].client->ps.saberBlocked = BLOCKED_NONE; + return didHit; + } + } + } + } + otherOwner = &g_entities[tr.entityNum]; + goto blockStuff; + } + else + {//damage the thing we hit + if (g_entities[tr.entityNum].client + && !g_entities[tr.entityNum].client->ps.weapon != WP_SABER )//fd.forcePowerLevel[FP_SABER_OFFENSE]) + { //not a "jedi", so make them suffer more + if ( dmg > SABER_NONATTACK_DAMAGE ) + { //don't bother increasing just for idle touch damage + dmg *= 1.5; + } + } + + if ( !d_saberSPStyleDamage.integer ) + { + if (g_entities[tr.entityNum].client && g_entities[tr.entityNum].client->ps.weapon == WP_SABER) + { //for jedi using the saber, half the damage (this comes with the increased default dmg debounce time) + if (g_gametype.integer != GT_SIEGE) + { //unless siege.. + if (dmg > SABER_NONATTACK_DAMAGE && !unblockable) + { //don't reduce damage if it's only 1, or if this is an unblockable attack + if (dmg == SABER_HITDAMAGE) + { //level 1 attack + dmg *= 0.7; + } + else + { + dmg *= 0.5; + } + } + } + } + } + + if (self->s.eType == ET_NPC && + g_entities[tr.entityNum].client && + self->client->playerTeam == g_entities[tr.entityNum].client->playerTeam) + { //Oops. Since he's an NPC, we'll be forgiving and cut the damage down. + dmg *= 0.2f; + } + + //store the damage, we'll apply it later + WP_SaberDamageAdd( tr.entityNum, dir, tr.endpos, dmg ); + + if (g_entities[tr.entityNum].client) + { + //Let jedi AI know if it hit an enemy + if ( self->enemy && self->enemy == &g_entities[tr.entityNum] ) + { + self->client->ps.saberEventFlags |= SEF_HITENEMY; + } + else + { + self->client->ps.saberEventFlags |= SEF_HITOBJECT; + } + } + + if ( d_saberSPStyleDamage.integer ) + { + } + else + { + self->client->ps.saberAttackWound = level.time + 100; + } + } + } + else if ((tr.fraction != 1 || tr.startsolid) && + (g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER) && + g_entities[tr.entityNum].r.contents != -1 && + g_entities[tr.entityNum].inuse) + { //saber clash + otherOwner = &g_entities[g_entities[tr.entityNum].r.ownerNum]; + + if (!otherOwner->inuse || !otherOwner->client) + { + return qfalse; + } + + if ( otherOwner + && otherOwner->client + && otherOwner->client->ps.saberInFlight ) + {//don't do extra collision checking vs sabers in air + } + else + {//hit an in-hand saber, do extra collision check against it + if ( d_saberSPStyleDamage.integer ) + {//use SP-style blade-collision test + if ( !WP_SabersIntersect( self, rSaberNum, rBladeNum, otherOwner, qfalse ) ) + {//sabers did not actually intersect + return qfalse; + } + } + else + {//MP-style + if (!G_SaberCollide(self, otherOwner, lastValidStart, + lastValidEnd, saberTrMins, saberTrMaxs, tr.endpos)) + { //detailed collision did not produce results... + return qfalse; + } + } + } + + if (OnSameTeam(self, otherOwner) && + !g_friendlySaber.integer) + { + return qfalse; + } + + if ((self->s.eType == ET_NPC || otherOwner->s.eType == ET_NPC) && //just make sure one of us is an npc + self->client->playerTeam == otherOwner->client->playerTeam && + g_gametype.integer != GT_SIEGE) + { //don't hit your teammate's sabers if you are an NPC. It can be rather annoying. + return qfalse; + } + + if (otherOwner->client->ps.duelInProgress && + otherOwner->client->ps.duelIndex != self->s.number) + { + return qfalse; + } + + if (self->client->ps.duelInProgress && + self->client->ps.duelIndex != otherOwner->s.number) + { + return qfalse; + } + + if ( g_debugSaberLocks.integer ) + { + WP_SabersCheckLock2( self, otherOwner, LOCK_RANDOM ); + return qtrue; + } + didHit = qtrue; + self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer; + + if (dmg <= SABER_NONATTACK_DAMAGE) + { + self->client->ps.saberIdleWound = level.time + g_saberDmgDelay_Idle.integer; + } + + saberDoClashEffect = qtrue; + VectorCopy( tr.endpos, saberClashPos ); + VectorCopy( tr.endpos, saberClashNorm ); + saberClashEventParm = 1; + + sabersClashed = qtrue; + saberHitSaber = qtrue; + saberHitFraction = tr.fraction; + + if (saberCheckKnockdown_Smashed(&g_entities[tr.entityNum], otherOwner, self, dmg)) + { //smashed it out of the air + return qfalse; + } + + //is this my thrown saber? + if ( self->client->ps.saberEntityNum + && self->client->ps.saberInFlight + && rSaberNum == 0 + && saberCheckKnockdown_Smashed( &g_entities[self->client->ps.saberEntityNum], self, otherOwner, dmg)) + { //they smashed it out of the air + return qfalse; + } + +blockStuff: + otherUnblockable = qfalse; + + if (otherOwner && otherOwner->client && otherOwner->client->ps.saberInFlight) + { + return qfalse; + } + + //this is a thrown saber, don't do any fancy saber-saber collision stuff + if ( self->client->ps.saberEntityNum + && self->client->ps.saberInFlight + && rSaberNum == 0 ) + { + return qfalse; + } + + otherSaberLevel = G_SaberAttackPower(otherOwner, SaberAttacking(otherOwner)); + + if (dmg > SABER_NONATTACK_DAMAGE && !unblockable && !otherUnblockable) + { + int lockFactor = g_saberLockFactor.integer; + + if (sabersClashed && Q_irand(1, 20) <= lockFactor) + { + if (!G_ClientIdleInWorld(otherOwner)) + { + if (WP_SabersCheckLock(self, otherOwner)) + { + self->client->ps.saberBlocked = BLOCKED_NONE; + otherOwner->client->ps.saberBlocked = BLOCKED_NONE; + return didHit; + } + } + } + } + + if (!otherOwner || !otherOwner->client) + { + return didHit; + } + + if (BG_SaberInSpecial(otherOwner->client->ps.saberMove)) + { + otherUnblockable = qtrue; + otherOwner->client->ps.saberBlocked = 0; + } + + if ( sabersClashed && + dmg > SABER_NONATTACK_DAMAGE && + selfSaberLevel < FORCE_LEVEL_3 && + !PM_SaberInBounce(otherOwner->client->ps.saberMove) && + !PM_SaberInParry(self->client->ps.saberMove) && + !PM_SaberInBrokenParry(self->client->ps.saberMove) && + !BG_SaberInSpecial(self->client->ps.saberMove) && + !PM_SaberInBounce(self->client->ps.saberMove) && + !PM_SaberInDeflect(self->client->ps.saberMove) && + !PM_SaberInReflect(self->client->ps.saberMove) && + !unblockable ) + { + //if (Q_irand(1, 10) <= 6) + if (1) //for now, just always try a deflect. (deflect func can cause bounces too) + { + if (!WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction)) + { + tryDeflectAgain = qtrue; //Failed the deflect, try it again if we can if the guy we're smashing goes into a parry and we don't break it + } + else + { + self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + didOffense = qtrue; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + didOffense = qtrue; + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Client %i clashed into client %i's saber, did BLOCKED_ATK_BOUNCE\n", self->s.number, otherOwner->s.number); + } +#endif + } + } + + if ( ((selfSaberLevel < FORCE_LEVEL_3 && ((tryDeflectAgain && Q_irand(1, 10) <= 3) || (!tryDeflectAgain && Q_irand(1, 10) <= 7))) || (Q_irand(1, 10) <= 1 && otherSaberLevel >= FORCE_LEVEL_3)) + && !PM_SaberInBounce(self->client->ps.saberMove) + + && !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) + && !BG_SaberInSpecial(otherOwner->client->ps.saberMove) + && !PM_SaberInBounce(otherOwner->client->ps.saberMove) + && !PM_SaberInDeflect(otherOwner->client->ps.saberMove) + && !PM_SaberInReflect(otherOwner->client->ps.saberMove) + + && (otherSaberLevel > FORCE_LEVEL_2 || ( otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= 3 && Q_irand(0, otherSaberLevel) )) + && !unblockable + && !otherUnblockable + && dmg > SABER_NONATTACK_DAMAGE + && !didOffense) //don't allow the person we're attacking to do this if we're making an unblockable attack + {//knockaways can make fast-attacker go into a broken parry anim if the ent is using fast or med. In MP, we also randomly decide this for level 3 attacks. + //Going to go ahead and let idle damage do simple knockaways. Looks sort of good that way. + //turn the parry into a knockaway + if (self->client->ps.saberEntityNum) //make sure he has his saber still + { + saberCheckKnockdown_BrokenParry(&g_entities[self->client->ps.saberEntityNum], self, otherOwner); + } + + if (!PM_SaberInParry(otherOwner->client->ps.saberMove)) + { + WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse); + otherOwner->client->ps.saberMove = BG_KnockawayForParry( otherOwner->client->ps.saberBlocked ); + otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + } + else + { + otherOwner->client->ps.saberMove = G_KnockawayForParry(otherOwner->client->ps.saberMove); //BG_KnockawayForParry( otherOwner->client->ps.saberBlocked ); + otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + } + + //make them (me) go into a broken parry + self->client->ps.saberMove = BG_BrokenParryForAttack( self->client->ps.saberMove ); + self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Client %i sent client %i into a reflected attack with a knockaway\n", otherOwner->s.number, self->s.number); + } +#endif + + didDefense = qtrue; + } + else if ((selfSaberLevel > FORCE_LEVEL_2 || unblockable) && //if we're doing a special attack, we can send them into a broken parry too (MP only) + ( otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] < selfSaberLevel || (otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == selfSaberLevel && (Q_irand(1, 10) >= otherSaberLevel*1.5 || unblockable)) ) && + PM_SaberInParry(otherOwner->client->ps.saberMove) && + !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) && + !PM_SaberInParry(self->client->ps.saberMove) && + !PM_SaberInBrokenParry(self->client->ps.saberMove) && + !PM_SaberInBounce(self->client->ps.saberMove) && + dmg > SABER_NONATTACK_DAMAGE && + !didOffense && + !otherUnblockable) + { //they are in a parry, and we are slamming down on them with a move of equal or greater force than their defense, so send them into a broken parry.. unless they are already in one. + if (otherOwner->client->ps.saberEntityNum) //make sure he has his saber still + { + saberCheckKnockdown_BrokenParry(&g_entities[otherOwner->client->ps.saberEntityNum], otherOwner, self); + } + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Client %i sent client %i into a broken parry\n", self->s.number, otherOwner->s.number); + } +#endif + + otherOwner->client->ps.saberMove = BG_BrokenParryForParry( otherOwner->client->ps.saberMove ); + otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + + didDefense = qtrue; + } + else if ((selfSaberLevel > FORCE_LEVEL_2) && //if we're doing a special attack, we can send them into a broken parry too (MP only) + //( otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] < selfSaberLevel || (otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == selfSaberLevel && (Q_irand(1, 10) >= otherSaberLevel*3 || unblockable)) ) && + otherSaberLevel >= FORCE_LEVEL_3 && + PM_SaberInParry(otherOwner->client->ps.saberMove) && + !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) && + !PM_SaberInParry(self->client->ps.saberMove) && + !PM_SaberInBrokenParry(self->client->ps.saberMove) && + !PM_SaberInBounce(self->client->ps.saberMove) && + !PM_SaberInDeflect(self->client->ps.saberMove) && + !PM_SaberInReflect(self->client->ps.saberMove) && + dmg > SABER_NONATTACK_DAMAGE && + !didOffense && + !unblockable) + { //they are in a parry, and we are slamming down on them with a move of equal or greater force than their defense, so send them into a broken parry.. unless they are already in one. +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Client %i bounced off of client %i's saber\n", self->s.number, otherOwner->s.number); + } +#endif + + if (!tryDeflectAgain) + { + if (!WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction)) + { + tryDeflectAgain = qtrue; + } + } + + didOffense = qtrue; + } + else if (SaberAttacking(otherOwner) && dmg > SABER_NONATTACK_DAMAGE && !BG_SaberInSpecial(otherOwner->client->ps.saberMove) && !didOffense && !otherUnblockable) + { //they were attacking and our saber hit their saber, make them bounce. But if they're in a special attack, leave them. + if (!PM_SaberInBounce(self->client->ps.saberMove) && + !PM_SaberInBounce(otherOwner->client->ps.saberMove) && + !PM_SaberInDeflect(self->client->ps.saberMove) && + !PM_SaberInDeflect(otherOwner->client->ps.saberMove) && + + !PM_SaberInReflect(self->client->ps.saberMove) && + !PM_SaberInReflect(otherOwner->client->ps.saberMove)) + { + int attackAdv, defendStr = G_PowerLevelForSaberAnim( otherOwner, 0, qtrue ), attackBonus = 0; + if ( otherOwner->client->ps.torsoAnim == BOTH_A1_SPECIAL + || otherOwner->client->ps.torsoAnim == BOTH_A2_SPECIAL + || otherOwner->client->ps.torsoAnim == BOTH_A3_SPECIAL ) + {//parry/block/break-parry bonus for single-style kata moves + defendStr++; + } + defendStr += Q_irand(0, otherOwner->client->saber[0].parryBonus ); + if ( otherOwner->client->saber[1].model + && otherOwner->client->saber[1].model[0] + && !otherOwner->client->ps.saberHolstered ) + { + defendStr += Q_irand(0, otherOwner->client->saber[1].parryBonus ); + } + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Client %i and client %i bounced off of each other's sabers\n", self->s.number, otherOwner->s.number); + } +#endif + + attackBonus = Q_irand(0, self->client->saber[0].breakParryBonus ); + if ( self->client->saber[1].model + && self->client->saber[1].model[0] + && !self->client->ps.saberHolstered ) + { + attackBonus += Q_irand(0, self->client->saber[1].breakParryBonus ); + } + attackAdv = (attackStr+attackBonus+self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])-(defendStr+otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]); + + if ( attackAdv > 1 ) + {//I won, he should knockaway + otherOwner->client->ps.saberMove = BG_BrokenParryForAttack( otherOwner->client->ps.saberMove ); + otherOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + } + else if ( attackAdv > 0 ) + {//I won, he should bounce, I should continue + otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + } + else if ( attackAdv < 1 ) + {//I lost, I get knocked away + self->client->ps.saberMove = BG_BrokenParryForAttack( self->client->ps.saberMove ); + self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + } + else if ( attackAdv < 0 ) + {//I lost, I bounce off + self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + } + else + {//even, both bounce off + self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + } + + didOffense = qtrue; + } + } + + if (d_saberGhoul2Collision.integer && !didDefense && dmg <= SABER_NONATTACK_DAMAGE && !otherUnblockable) //with perpoly, it looks pretty weird to have clash flares coming off the guy's face and whatnot + { + if (!PM_SaberInParry(otherOwner->client->ps.saberMove) && + !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) && + !BG_SaberInSpecial(otherOwner->client->ps.saberMove) && + !PM_SaberInBounce(otherOwner->client->ps.saberMove) && + !PM_SaberInDeflect(otherOwner->client->ps.saberMove) && + !PM_SaberInReflect(otherOwner->client->ps.saberMove)) + { + WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse); + otherOwner->client->ps.saberEventFlags |= SEF_PARRIED; + } + } + else if (!didDefense && dmg > SABER_NONATTACK_DAMAGE && !otherUnblockable) //if not more than idle damage, don't even bother blocking. + { //block + if (!PM_SaberInParry(otherOwner->client->ps.saberMove) && + !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) && + !BG_SaberInSpecial(otherOwner->client->ps.saberMove) && + !PM_SaberInBounce(otherOwner->client->ps.saberMove) && + !PM_SaberInDeflect(otherOwner->client->ps.saberMove) && + !PM_SaberInReflect(otherOwner->client->ps.saberMove)) + { + qboolean crushTheParry = qfalse; + + if (unblockable) + { //It's unblockable. So send us into a broken parry immediately. + crushTheParry = qtrue; + } + + if (!SaberAttacking(otherOwner)) + { + int otherIdleStr = otherOwner->client->ps.fd.saberAnimLevel; + if ( otherIdleStr == SS_DUAL + || otherIdleStr == SS_STAFF ) + { + otherIdleStr = SS_MEDIUM; + } + + WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse); + otherOwner->client->ps.saberEventFlags |= SEF_PARRIED; + self->client->ps.saberEventFlags |= SEF_BLOCKED; + + if ( attackStr+self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > otherIdleStr+otherOwner->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] ) + { + crushTheParry = qtrue; + } + else + { + tryDeflectAgain = qtrue; + } + } + else if (selfSaberLevel > otherSaberLevel || + (selfSaberLevel == otherSaberLevel && Q_irand(1, 10) <= 2)) + { //they are attacking, and we managed to make them break + //Give them a parry, so we can later break it. + WP_SaberBlockNonRandom(otherOwner, tr.endpos, qfalse); + crushTheParry = qtrue; + + if (otherOwner->client->ps.saberEntityNum) //make sure he has his saber still + { + saberCheckKnockdown_BrokenParry(&g_entities[otherOwner->client->ps.saberEntityNum], otherOwner, self); + } + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Client %i forced client %i into a broken parry with a stronger attack\n", self->s.number, otherOwner->s.number); + } +#endif + } + else + { //They are attacking, so are we, and obviously they have an attack level higher than or equal to ours + if (selfSaberLevel == otherSaberLevel) + { //equal level, try to bounce off each other's sabers + if (!didOffense && + !PM_SaberInParry(self->client->ps.saberMove) && + !PM_SaberInBrokenParry(self->client->ps.saberMove) && + !BG_SaberInSpecial(self->client->ps.saberMove) && + !PM_SaberInBounce(self->client->ps.saberMove) && + !PM_SaberInDeflect(self->client->ps.saberMove) && + !PM_SaberInReflect(self->client->ps.saberMove) && + !unblockable) + { + self->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + didOffense = qtrue; + } + if (!didDefense && + !PM_SaberInParry(otherOwner->client->ps.saberMove) && + !PM_SaberInBrokenParry(otherOwner->client->ps.saberMove) && + !BG_SaberInSpecial(otherOwner->client->ps.saberMove) && + !PM_SaberInBounce(otherOwner->client->ps.saberMove) && + !PM_SaberInDeflect(otherOwner->client->ps.saberMove) && + !PM_SaberInReflect(otherOwner->client->ps.saberMove) && + !unblockable) + { + otherOwner->client->ps.saberBlocked = BLOCKED_ATK_BOUNCE; + } +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Equal attack level bounce/deflection for clients %i and %i\n", self->s.number, otherOwner->s.number); + } +#endif + + self->client->ps.saberEventFlags |= SEF_DEFLECTED; + otherOwner->client->ps.saberEventFlags |= SEF_DEFLECTED; + } + else if ((level.time - otherOwner->client->lastSaberStorageTime) < 500 && !unblockable) //make sure the stored saber data is updated + { //They are higher, this means they can actually smash us into a broken parry + //Using reflected anims instead now + self->client->ps.saberMove = BG_BrokenParryForAttack(self->client->ps.saberMove); + self->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + + if (self->client->ps.saberEntityNum) //make sure he has his saber still + { + saberCheckKnockdown_BrokenParry(&g_entities[self->client->ps.saberEntityNum], self, otherOwner); + } + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Client %i hit client %i's stronger attack, was forced into a broken parry\n", self->s.number, otherOwner->s.number); + } +#endif + + otherOwner->client->ps.saberEventFlags &= ~SEF_BLOCKED; + + didOffense = qtrue; + } + } + + if (crushTheParry && PM_SaberInParry(G_GetParryForBlock(otherOwner->client->ps.saberBlocked))) + { //This means that the attack actually hit our saber, and we went to block it. + //But, one of the above cases says we actually can't. So we will be smashed into a broken parry instead. + otherOwner->client->ps.saberMove = BG_BrokenParryForParry( G_GetParryForBlock(otherOwner->client->ps.saberBlocked) ); + otherOwner->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + + otherOwner->client->ps.saberEventFlags &= ~SEF_PARRIED; + self->client->ps.saberEventFlags &= ~SEF_BLOCKED; + +#ifndef FINAL_BUILD + if (g_saberDebugPrint.integer) + { + Com_Printf("Client %i broke through %i's parry with a special or stronger attack\n", self->s.number, otherOwner->s.number); + } +#endif + } + else if (PM_SaberInParry(G_GetParryForBlock(otherOwner->client->ps.saberBlocked)) && !didOffense && tryDeflectAgain) + { //We want to try deflecting again because the other is in the parry and we haven't made any new moves + int preMove = otherOwner->client->ps.saberMove; + + otherOwner->client->ps.saberMove = G_GetParryForBlock(otherOwner->client->ps.saberBlocked); + WP_GetSaberDeflectionAngle(self, otherOwner, tr.fraction); + otherOwner->client->ps.saberMove = preMove; + } + } + } + + self->client->ps.saberAttackWound = level.time + g_saberDmgDelay_Wound.integer; + } + + return didHit; +} + +GAME_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; +} + +#define MAX_SABER_SWING_INC 0.33f +void G_SPSaberDamageTraceLerped( gentity_t *self, int saberNum, int bladeNum, vec3_t baseNew, vec3_t endNew, int clipmask ) +{ + vec3_t baseOld, endOld; + vec3_t mp1, mp2; + vec3_t md1, md2; + + VectorCopy( self->client->saber[saberNum].blade[bladeNum].trail.base, baseOld ); + VectorCopy( self->client->saber[saberNum].blade[bladeNum].trail.tip, endOld ); + + VectorCopy( baseOld, mp1 ); + VectorCopy( baseNew, mp2 ); + VectorSubtract( endOld, baseOld, md1 ); + VectorNormalize( md1 ); + VectorSubtract( endNew, baseNew, md2 ); + VectorNormalize( md2 ); + + saberHitWall = qfalse; + saberHitSaber = qfalse; + saberHitFraction = 1.0f; + if ( VectorCompare2( baseOld, baseNew ) && VectorCompare2( endOld, endNew ) ) + {//no diff + CheckSaberDamage( self, saberNum, bladeNum, baseNew, endNew, qfalse, clipmask, qfalse ); + } + else + {//saber moved, lerp + float step = 8, stepsize = 8;//aveLength, + vec3_t ma1, ma2, md2ang, curBase1, curBase2; + int xx; + vec3_t curMD1, curMD2;//, mdDiff, dirDiff; + float dirInc, curDirFrac; + vec3_t baseDiff, bladePointOld, bladePointNew; + qboolean extrapolate = qtrue; + + //do the trace at the base first + VectorCopy( baseOld, bladePointOld ); + VectorCopy( baseNew, bladePointNew ); + CheckSaberDamage( self, saberNum, bladeNum, bladePointOld, bladePointNew, qfalse, clipmask, qtrue ); + + //if hit a saber, shorten rest of traces to match + if ( saberHitFraction < 1.0f ) + { + //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, self->client->saber[saberNum].blade[bladeNum].lengthMax, 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 + if ( BG_SaberInAttack( self->client->ps.saberMove ) + || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) + || BG_SpinningSaberAnim( self->client->ps.torsoAnim ) + || BG_InSpecialJump( self->client->ps.torsoAnim ) ) + //|| (g_timescale->value<1.0f&&BG_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 ); + + //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 <= self->client->saber[saberNum].blade[bladeNum].lengthMax /*&& step < self->client->saber[saberNum].blade[bladeNum].lengthOld*/; step += stepsize ) + { + VectorMA( curBase1, step, curMD1, bladePointOld ); + VectorMA( curBase2, step, curMD2, bladePointNew ); + + if ( step+stepsize >= self->client->saber[saberNum].blade[bladeNum].lengthMax ) + { + extrapolate = qfalse; + } + //do the damage trace + CheckSaberDamage( self, saberNum, bladeNum, bladePointOld, bladePointNew, qfalse, clipmask, extrapolate ); + /* + 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.0f ) + { + vec3_t curMA1, curMA2; + //adjust muzzle endpoint + VectorSubtract( mp2, mp1, baseDiff ); + VectorMA( mp1, saberHitFraction, baseDiff, baseNew ); + VectorMA( baseNew, self->client->saber[saberNum].blade[bladeNum].lengthMax, curMD2, endNew ); + //adjust muzzleDir... + 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 ); + saberHitSaber = qtrue; + } + if (saberHitWall) + { + break; + } + } + if ( saberHitWall || saberHitSaber ) + { + 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; + } + */ + } +} + +#include "../namespace_begin.h" +qboolean BG_SaberInTransitionAny( int move ); +#include "../namespace_end.h" + +qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower ); +qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV ); +qboolean Jedi_WaitingAmbush( gentity_t *self ); +void Jedi_Ambush( gentity_t *self ); +evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist ); +void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd ) +{ + float dist; + gentity_t *ent, *incoming = NULL; + int 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; + float dot1, dot2; + float lookTDist = -1; + gentity_t *lookT = NULL; + qboolean doFullRoutine = qtrue; + + //keep this updated even if we don't get below + 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 + self->client->ps.hasLookTarget = qfalse; + } + + if ( self->client->ps.weapon != WP_SABER && self->client->NPC_class != CLASS_BOBAFETT ) + { + doFullRoutine = qfalse; + } + else if ( self->client->ps.saberInFlight ) + { + doFullRoutine = qfalse; + } + else if ( self->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersActive&(1<client->ps.weaponTime > 0) + { //don't autoblock while busy with stuff + return; + } + + if ( !self->client->saber[0].activeBlocking ) + {//can't actively block with this saber type + 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 ( BG_SabersOff( &self->client->ps ) && self->client->NPC_class != CLASS_BOBAFETT ) + { + if ( self->s.eType != ET_NPC ) + {//player doesn't auto-activate + doFullRoutine = qfalse; + } + } + + if ( self->s.eType == ET_PLAYER ) + {//don't do this if already attacking! + if ( ucmd->buttons & BUTTON_ATTACK + || BG_SaberInAttack( self->client->ps.saberMove ) + || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) + || BG_SaberInTransitionAny( self->client->ps.saberMove )) + { + doFullRoutine = qfalse; + } + } + + if ( self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time ) + {//can't block while gripping (FIXME: or should it break the grip? Pain should break the grip, I think...) + doFullRoutine = qfalse; + } + + fwdangles[1] = self->client->ps.viewangles[1]; + AngleVectors( fwdangles, forward, NULL, NULL ); + + for ( i = 0 ; i < 3 ; i++ ) + { + mins[i] = self->r.currentOrigin[i] - radius; + maxs[i] = self->r.currentOrigin[i] + radius; + } + + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + closestDist = radius; + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + ent = &g_entities[entityList[ e ]]; + + if (ent == self) + continue; + + //as long as we're here I'm going to get a looktarget too, I guess. -rww + if (self->s.eType == ET_PLAYER && + ent->client && + (ent->s.eType == ET_NPC || ent->s.eType == ET_PLAYER) && + !OnSameTeam(ent, self) && + ent->client->sess.sessionTeam != TEAM_SPECTATOR && + !(ent->client->ps.pm_flags & PMF_FOLLOW) && + (ent->s.eType != ET_NPC || ent->s.NPC_class != CLASS_VEHICLE) && //don't look at vehicle NPCs + ent->health > 0) + { //seems like a valid enemy to look at. + vec3_t vecSub; + float vecLen; + + VectorSubtract(self->client->ps.origin, ent->client->ps.origin, vecSub); + vecLen = VectorLength(vecSub); + + if (lookTDist == -1 || vecLen < lookTDist) + { + trace_t tr; + vec3_t myEyes; + + VectorCopy(self->client->ps.origin, myEyes); + myEyes[2] += self->client->ps.viewheight; + + trap_Trace(&tr, myEyes, NULL, NULL, ent->client->ps.origin, self->s.number, MASK_PLAYERSOLID); + + if (tr.fraction == 1.0f || tr.entityNum == ent->s.number) + { //we have a clear line of sight to him, so it's all good. + lookT = ent; + lookTDist = vecLen; + } + } + } + + if (!doFullRoutine) + { //don't care about the rest then + continue; + } + + if (ent->r.ownerNum == self->s.number) + continue; + if ( !(ent->inuse) ) + continue; + if ( ent->s.eType != ET_MISSILE && !(ent->s.eFlags&EF_MISSILE_STICK) ) + {//not a normal projectile + gentity_t *pOwner; + + if (ent->r.ownerNum < 0 || ent->r.ownerNum >= ENTITYNUM_WORLD) + { //not going to be a client then. + continue; + } + + pOwner = &g_entities[ent->r.ownerNum]; + + if (!pOwner->inuse || !pOwner->client) + { + continue; //not valid cl owner + } + + if (!pOwner->client->ps.saberEntityNum || + !pOwner->client->ps.saberInFlight || + pOwner->client->ps.saberEntityNum != ent->s.number) + { //the saber is knocked away and/or not flying actively, or this ent is not the cl's saber ent at all + continue; + } + + //If we get here then it's ok to be treated as a thrown saber, I guess. + } + else + { + if ( ent->s.pos.trType == TR_STATIONARY && self->s.eType == ET_PLAYER ) + {//nothing you can do with a stationary missile if you're the player + continue; + } + } + + //see if they're in front of me + VectorSubtract( ent->r.currentOrigin, self->r.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 )) ) + {//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.fd.forceJumpCharge = 480; + } + else if ( self->client->NPC_class != CLASS_BOBAFETT ) + {//FIXME: check forcePushRadius[NPC->client->ps.forcePowerLevel[FP_PUSH]] + 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.eType == ET_PLAYER ) + {//players don't auto-handle these at all + continue; + } + else + { + //if ( ent->s.pos.trType == TR_STATIONARY && (ent->s.eFlags&EF_MISSILE_STICK) + // && self->client->NPC_class != CLASS_BOBAFETT ) + if (0) //Maybe handle this later? + {//a placed explosive like a tripmine or detpack + if ( InFOV3( ent->r.currentOrigin, self->client->renderInfo.eyePoint, self->client->ps.viewangles, 90, 90 ) ) + {//in front of me + if ( G_ClearLOS4( 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->flags |= FL_BOUNCE_HALF; + AngleVectors( ent->r.currentAngles, throwDir, NULL, NULL ); + VectorMA( ent->r.currentOrigin, ent->r.maxs[0]+4, throwDir, ent->r.currentOrigin ); + VectorCopy( ent->r.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->r.currentOrigin, ent->s.pos.trBase ); + ent->r.ownerNum = self->s.number; + // make it explode, but with less damage + ent->splashDamage /= 3; + ent->splashRadius /= 3; + //ent->think = 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 )) ) + {//NPCs try to evade it + self->client->ps.fd.forceJumpCharge = 480; + } + else if ( self->client->NPC_class != CLASS_BOBAFETT ) + {//else, try to force-throw it away + //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.eType == ET_PLAYER ) + {//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->r.currentOrigin, traceTo ); + traceTo[2] = self->r.absmax[2] - 4; + trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.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->r.currentOrigin, radius, entDir, traceTo ); + trap_Trace( &trace, ent->r.currentOrigin, ent->r.mins, ent->r.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.eType == ET_NPC ) + {//An NPC + if ( self->NPC && !self->enemy && ent->r.ownerNum != ENTITYNUM_NONE ) + { + gentity_t *owner = &g_entities[ent->r.ownerNum]; + if ( owner->health >= 0 && (!owner->client || owner->client->playerTeam != self->client->playerTeam) ) + { + G_SetEnemy( self, 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 (self->s.eType == ET_NPC && self->localAnimIndex <= 1) + { //humanoid NPCs don't set angles based on server angles for looking, unlike other NPCs + if (self->client && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD) + { + lookT = &g_entities[self->client->renderInfo.lookTarget]; + } + } + + if (lookT) + { //we got a looktarget at some point so we'll assign it then. + 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 + self->client->ps.hasLookTarget = qtrue; + self->client->ps.lookTarget = lookT->s.number; + } + } + + if (!doFullRoutine) + { //then we're done now + return; + } + + if ( incoming ) + { + if ( self->NPC /*&& !G_ControlledByPlayer( self )*/ ) + { + if ( Jedi_WaitingAmbush( self ) ) + { + Jedi_Ambush( self ); + } + if ( self->client->NPC_class == CLASS_BOBAFETT + && (self->client->ps.eFlags2&EF2_FLYING)//moveType == MT_FLYSWIM + && incoming->methodOfDeath != MOD_ROCKET_HOMING ) + {//a hovering Boba Fett, not a tracking rocket + if ( !Q_irand( 0, 1 ) ) + {//strafe + self->NPC->standTime = 0; + self->client->ps.fd.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.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + Q_irand( 1000, 2000 ); + } + } + else if ( Jedi_SaberBlockGo( self, &self->NPC->last_ucmd, NULL, NULL, incoming, 0.0f ) != EVASION_NONE ) + {//make sure to turn on your saber if it's not on + if ( self->client->NPC_class != CLASS_BOBAFETT ) + { + //self->client->ps.SaberActivate(); + WP_ActivateSaber(self); + } + } + } + else//player + { + gentity_t *owner = &g_entities[incoming->r.ownerNum]; + + WP_SaberBlockNonRandom( self, incoming->r.currentOrigin, qtrue ); + if ( owner && owner->client && (!self->enemy || self->enemy->s.weapon != WP_SABER) )//keep enemy jedi over shooters + { + self->enemy = owner; + //NPC_SetLookTarget( self, owner->s.number, level.time+1000 ); + //player looktargetting done differently + } + } + } +} + +#define MIN_SABER_SLICE_DISTANCE 50 + +#define MIN_SABER_SLICE_RETURN_DISTANCE 30 + +#define SABER_THROWN_HIT_DAMAGE 30 +#define SABER_THROWN_RETURN_HIT_DAMAGE 5 + +void thrownSaberTouch (gentity_t *saberent, gentity_t *other, trace_t *trace); + +static GAME_INLINE qboolean CheckThrownSaberDamaged(gentity_t *saberent, gentity_t *saberOwner, gentity_t *ent, int dist, int returning, qboolean noDCheck) +{ + vec3_t vecsub; + float veclen; + gentity_t *te; + + if (saberOwner && saberOwner->client && saberOwner->client->ps.saberAttackWound > level.time) + { + return qfalse; + } + + if (ent && ent->client && ent->inuse && ent->s.number != saberOwner->s.number && + ent->health > 0 && ent->takedamage && + trap_InPVS(ent->client->ps.origin, saberent->r.currentOrigin) && + ent->client->sess.sessionTeam != TEAM_SPECTATOR && + (ent->client->pers.connected || ent->s.eType == ET_NPC)) + { //hit a client + if (ent->inuse && ent->client && + ent->client->ps.duelInProgress && + ent->client->ps.duelIndex != saberOwner->s.number) + { + return qfalse; + } + + if (ent->inuse && ent->client && + saberOwner->client->ps.duelInProgress && + saberOwner->client->ps.duelIndex != ent->s.number) + { + return qfalse; + } + + VectorSubtract(saberent->r.currentOrigin, ent->client->ps.origin, vecsub); + veclen = VectorLength(vecsub); + + if (veclen < dist) + { //within range + trace_t tr; + + trap_Trace(&tr, saberent->r.currentOrigin, NULL, NULL, ent->client->ps.origin, saberent->s.number, MASK_SHOT); + + if (tr.fraction == 1 || tr.entityNum == ent->s.number) + { //Slice them + if (!saberOwner->client->ps.isJediMaster && WP_SaberCanBlock(ent, tr.endpos, 0, MOD_SABER, qfalse, 999)) + { //they blocked it + WP_SaberBlockNonRandom(ent, tr.endpos, qfalse); + + te = G_TempEntity( tr.endpos, EV_SABER_BLOCK ); + VectorCopy(tr.endpos, te->s.origin); + VectorCopy(tr.plane.normal, te->s.angles); + if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2]) + { + te->s.angles[1] = 1; + } + te->s.eventParm = 1; + + if (saberCheckKnockdown_Thrown(saberent, saberOwner, &g_entities[tr.entityNum])) + { //it was knocked out of the air + return qfalse; + } + + if (!returning) + { //return to owner if blocked + thrownSaberTouch(saberent, saberent, NULL); + } + + saberOwner->client->ps.saberAttackWound = level.time + 500; + return qfalse; + } + else + { //a good hit + vec3_t dir; + + VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir); + VectorNormalize(dir); + + if (!dir[0] && !dir[1] && !dir[2]) + { + dir[1] = 1; + } + + if (saberOwner->client->ps.isJediMaster) + { //2x damage for the Jedi Master + G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage*2, 0, MOD_SABER); + } + else + { + G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, saberent->damage, 0, MOD_SABER); + } + + te = G_TempEntity( tr.endpos, EV_SABER_HIT ); + te->s.otherEntityNum = ent->s.number; + te->s.otherEntityNum2 = saberOwner->s.number; + VectorCopy(tr.endpos, te->s.origin); + VectorCopy(tr.plane.normal, te->s.angles); + if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2]) + { + te->s.angles[1] = 1; + } + + te->s.eventParm = 1; + + if (!returning) + { //return to owner if blocked + thrownSaberTouch(saberent, saberent, NULL); + } + } + + saberOwner->client->ps.saberAttackWound = level.time + 500; + } + } + } + else if (ent && !ent->client && ent->inuse && ent->takedamage && ent->health > 0 && ent->s.number != saberOwner->s.number && + ent->s.number != saberent->s.number && (noDCheck ||trap_InPVS(ent->r.currentOrigin, saberent->r.currentOrigin))) + { //hit a non-client + + if (noDCheck) + { + veclen = 0; + } + else + { + VectorSubtract(saberent->r.currentOrigin, ent->r.currentOrigin, vecsub); + veclen = VectorLength(vecsub); + } + + if (veclen < dist) + { + trace_t tr; + vec3_t entOrigin; + + if (ent->s.eType == ET_MOVER) + { + VectorSubtract( ent->r.absmax, ent->r.absmin, entOrigin ); + VectorMA( ent->r.absmin, 0.5, entOrigin, entOrigin ); + VectorAdd( ent->r.absmin, ent->r.absmax, entOrigin ); + VectorScale( entOrigin, 0.5f, entOrigin ); + } + else + { + VectorCopy(ent->r.currentOrigin, entOrigin); + } + + trap_Trace(&tr, saberent->r.currentOrigin, NULL, NULL, entOrigin, saberent->s.number, MASK_SHOT); + + if (tr.fraction == 1 || tr.entityNum == ent->s.number) + { + vec3_t dir; + + VectorSubtract(tr.endpos, entOrigin, dir); + VectorNormalize(dir); + + if (ent->s.eType == ET_NPC) + { //an animent + G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 40, 0, MOD_SABER); + } + else + { + G_Damage(ent, saberOwner, saberOwner, dir, tr.endpos, 5, 0, MOD_SABER); + } + + te = G_TempEntity( tr.endpos, EV_SABER_HIT ); + te->s.otherEntityNum = ENTITYNUM_NONE; //don't do this for throw damage + //te->s.otherEntityNum = ent->s.number; + //te->s.otherEntityNum2 = saberOwner->s.number; + VectorCopy(tr.endpos, te->s.origin); + VectorCopy(tr.plane.normal, te->s.angles); + if (!te->s.angles[0] && !te->s.angles[1] && !te->s.angles[2]) + { + te->s.angles[1] = 1; + } + + if (ent->s.eType == ET_MOVER) + { + //I suppose I could tie this into the saberblock event, but I'm tired of adding flags to that thing. + gentity_t *teS = G_TempEntity( te->s.origin, EV_SABER_CLASHFLARE ); + VectorCopy(te->s.origin, teS->s.origin); + + te->s.eventParm = 0; + } + else + { + te->s.eventParm = 1; + } + + if (!returning) + { //return to owner if blocked + thrownSaberTouch(saberent, saberent, NULL); + } + + saberOwner->client->ps.saberAttackWound = level.time + 500; + } + } + } + + return qtrue; +} + +static GAME_INLINE void saberCheckRadiusDamage(gentity_t *saberent, int returning) +{ //we're going to cheat and damage players within the saber's radius, just for the sake of doing things more "efficiently" (and because the saber entity has no server g2 instance) + int i = 0; + int dist = 0; + gentity_t *ent; + gentity_t *saberOwner = &g_entities[saberent->r.ownerNum]; + + if (returning && returning != 2) + { + dist = MIN_SABER_SLICE_RETURN_DISTANCE; + } + else + { + dist = MIN_SABER_SLICE_DISTANCE; + } + + if (!saberOwner || !saberOwner->client) + { + return; + } + + if (saberOwner->client->ps.saberAttackWound > level.time) + { + return; + } + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + CheckThrownSaberDamaged(saberent, saberOwner, ent, dist, returning, qfalse); + + i++; + } +} + +#define THROWN_SABER_COMP + +static GAME_INLINE void saberMoveBack( gentity_t *ent, qboolean goingBack ) +{ + vec3_t origin, oldOrg; + + ent->s.pos.trType = TR_LINEAR; + + VectorCopy( ent->r.currentOrigin, oldOrg ); + // get current position + BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + //Get current angles? + BG_EvaluateTrajectory( &ent->s.apos, level.time, ent->r.currentAngles ); + + //compensation test code.. +#ifdef THROWN_SABER_COMP + if (!goingBack && ent->s.pos.trType != TR_GRAVITY) + { //acts as a fallback in case touch code fails, keeps saber from going through things between predictions + float originalLength = 0; + int iCompensationLength = 32; + trace_t tr; + vec3_t mins, maxs; + vec3_t calcComp, compensatedOrigin; + VectorSet( mins, -24.0f, -24.0f, -8.0f ); + VectorSet( maxs, 24.0f, 24.0f, 8.0f ); + + VectorSubtract(origin, oldOrg, calcComp); + originalLength = VectorLength(calcComp); + + VectorNormalize(calcComp); + + compensatedOrigin[0] = oldOrg[0] + calcComp[0]*(originalLength+iCompensationLength); + compensatedOrigin[1] = oldOrg[1] + calcComp[1]*(originalLength+iCompensationLength); + compensatedOrigin[2] = oldOrg[2] + calcComp[2]*(originalLength+iCompensationLength); + + trap_Trace(&tr, oldOrg, mins, maxs, compensatedOrigin, ent->r.ownerNum, MASK_PLAYERSOLID); + + if ((tr.fraction != 1 || tr.startsolid || tr.allsolid) && tr.entityNum != ent->r.ownerNum && !(g_entities[tr.entityNum].r.contents & CONTENTS_LIGHTSABER)) + { + VectorClear(ent->s.pos.trDelta); + + //Unfortunately doing this would defeat the purpose of the compensation. We will have to settle for a jerk on the client. + //VectorCopy( origin, ent->r.currentOrigin ); + + //we'll skip the dist check, since we don't really care about that (we just hit it physically) + CheckThrownSaberDamaged(ent, &g_entities[ent->r.ownerNum], &g_entities[tr.entityNum], 256, 0, qtrue); + + if (ent->s.pos.trType == TR_GRAVITY) + { //got blocked and knocked away in the damage func + return; + } + + tr.startsolid = 0; + if (tr.entityNum == ENTITYNUM_NONE) + { //eh, this is a filthy lie. (obviously it had to hit something or it wouldn't be in here, so we'll say it hit the world) + tr.entityNum = ENTITYNUM_WORLD; + } + thrownSaberTouch(ent, &g_entities[tr.entityNum], &tr); + return; + } + } +#endif + + VectorCopy( origin, ent->r.currentOrigin ); +} + +void SaberBounceSound( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + VectorCopy(self->r.currentAngles, self->s.apos.trBase); + self->s.apos.trBase[PITCH] = 90; +} + +void DeadSaberThink(gentity_t *saberent) +{ + if (saberent->speed < level.time) + { + saberent->think = G_FreeEntity; + saberent->nextthink = level.time; + return; + } + + G_RunObject(saberent); +} + +void MakeDeadSaber(gentity_t *ent) +{ //spawn a "dead" saber entity here so it looks like the saber fell out of the air. + //This entity will remove itself after a very short time period. + vec3_t startorg; + vec3_t startang; + gentity_t *saberent; + gentity_t *owner = NULL; + + if (g_gametype.integer == GT_JEDIMASTER) + { //never spawn a dead saber in JM, because the only saber on the level is really a world object + //G_Sound(ent, CHAN_AUTO, saberOffSound); + return; + } + + saberent = G_Spawn(); + + VectorCopy(ent->r.currentOrigin, startorg); + VectorCopy(ent->r.currentAngles, startang); + + saberent->classname = "deadsaber"; + + saberent->r.svFlags = SVF_USE_CURRENT_ORIGIN; + saberent->r.ownerNum = ent->s.number; + + saberent->clipmask = MASK_PLAYERSOLID; + saberent->r.contents = CONTENTS_TRIGGER;//0; + + VectorSet( saberent->r.mins, -3.0f, -3.0f, -1.5f ); + VectorSet( saberent->r.maxs, 3.0f, 3.0f, 1.5f ); + + saberent->touch = SaberBounceSound; + + saberent->think = DeadSaberThink; + saberent->nextthink = level.time; + + VectorCopy(startorg, saberent->s.pos.trBase); + VectorCopy(startang, saberent->s.apos.trBase); + + VectorCopy(startorg, saberent->s.origin); + VectorCopy(startang, saberent->s.angles); + + VectorCopy(startorg, saberent->r.currentOrigin); + VectorCopy(startang, saberent->r.currentAngles); + + saberent->s.apos.trType = TR_GRAVITY; + saberent->s.apos.trDelta[0] = Q_irand(200, 800); + saberent->s.apos.trDelta[1] = Q_irand(200, 800); + saberent->s.apos.trDelta[2] = Q_irand(200, 800); + saberent->s.apos.trTime = level.time-50; + + saberent->s.pos.trType = TR_GRAVITY; + saberent->s.pos.trTime = level.time-50; + saberent->flags = FL_BOUNCE_HALF; + if (ent->r.ownerNum >= 0 && ent->r.ownerNum < ENTITYNUM_WORLD) + { + owner = &g_entities[ent->r.ownerNum]; + + if (owner->inuse && owner->client && + owner->client->saber[0].model[0]) + { + WP_SaberAddG2Model( saberent, owner->client->saber[0].model, owner->client->saber[0].skin ); + } + else + { + //WP_SaberAddG2Model( saberent, NULL, 0 ); + //argh!!!! + G_FreeEntity(saberent); + return; + } + } + + saberent->s.modelGhoul2 = 1; + saberent->s.g2radius = 20; + + saberent->s.eType = ET_MISSILE; + saberent->s.weapon = WP_SABER; + + saberent->speed = level.time + 4000; + + saberent->bounceCount = 12; + + //fall off in the direction the real saber was headed + VectorCopy(ent->s.pos.trDelta, saberent->s.pos.trDelta); + + saberMoveBack(saberent, qtrue); + saberent->s.pos.trType = TR_GRAVITY; + + trap_LinkEntity(saberent); +} + +#define MAX_LEAVE_TIME 20000 + +void saberReactivate(gentity_t *saberent, gentity_t *saberOwner); +void saberBackToOwner(gentity_t *saberent); + +void DownedSaberThink(gentity_t *saberent) +{ + gentity_t *saberOwn = NULL; + qboolean notDisowned = qfalse; + qboolean pullBack = qfalse; + + saberent->nextthink = level.time; + + if (saberent->r.ownerNum == ENTITYNUM_NONE) + { + MakeDeadSaber(saberent); + + saberent->think = G_FreeEntity; + saberent->nextthink = level.time; + return; + } + + saberOwn = &g_entities[saberent->r.ownerNum]; + + if (!saberOwn || + !saberOwn->inuse || + !saberOwn->client || + saberOwn->client->sess.sessionTeam == TEAM_SPECTATOR || + (saberOwn->client->ps.pm_flags & PMF_FOLLOW)) + { + MakeDeadSaber(saberent); + + saberent->think = G_FreeEntity; + saberent->nextthink = level.time; + return; + } + + if (saberOwn->client->ps.saberEntityNum) + { + if (saberOwn->client->ps.saberEntityNum == saberent->s.number) + { //owner shouldn't have this set if we're thinking in here. Must've fallen off a cliff and instantly respawned or something. + notDisowned = qtrue; + } + else + { //This should never happen, but just in case.. + assert(!"ULTRA BAD THING"); + MakeDeadSaber(saberent); + + saberent->think = G_FreeEntity; + saberent->nextthink = level.time; + return; + } + } + + if (notDisowned || saberOwn->health < 1 || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { //He's dead, just go back to our normal saber status + saberOwn->client->ps.saberEntityNum = saberOwn->client->saberStoredIndex; + + //MakeDeadSaber(saberent); //spawn a dead saber on top of where we are now. The "bodyqueue" method. + //Actually this will get taken care of when the thrown saber func sees we're dead. + +#ifdef _DEBUG + if (saberOwn->client->saberStoredIndex != saberent->s.number) + { //I'm paranoid. + assert(!"Bad saber index!!!"); + } +#endif + + saberReactivate(saberent, saberOwn); + + if (saberOwn->health < 1) + { + saberOwn->client->ps.saberInFlight = qfalse; + MakeDeadSaber(saberent); + } + + saberent->touch = SaberGotHit; + saberent->think = SaberUpdateSelf; + saberent->genericValue5 = 0; + saberent->nextthink = level.time; + + saberent->r.svFlags |= (SVF_NOCLIENT); + //saberent->r.contents = CONTENTS_LIGHTSABER; + saberent->s.loopSound = 0; + saberent->s.loopIsSoundset = qfalse; + + if (saberOwn->health > 0) + { //only set this if he's alive. If dead we want to reflect the lack of saber on the corpse, as he died with his saber out. + saberOwn->client->ps.saberInFlight = qfalse; + WP_SaberRemoveG2Model( saberent ); + } + saberOwn->client->ps.saberEntityState = 0; + saberOwn->client->ps.saberThrowDelay = level.time + 500; + saberOwn->client->ps.saberCanThrow = qfalse; + + return; + } + + if (saberOwn->client->saberKnockedTime < level.time && (saberOwn->client->pers.cmd.buttons & BUTTON_ATTACK)) + { //He wants us back + pullBack = qtrue; + } + else if ((level.time - saberOwn->client->saberKnockedTime) > MAX_LEAVE_TIME) + { //Been sitting around for too long, go back no matter what he wants. + pullBack = qtrue; + } + + if (pullBack) + { //Get going back to the owner. + saberOwn->client->ps.saberEntityNum = saberOwn->client->saberStoredIndex; + +#ifdef _DEBUG + if (saberOwn->client->saberStoredIndex != saberent->s.number) + { //I'm paranoid. + assert(!"Bad saber index!!!"); + } +#endif + saberReactivate(saberent, saberOwn); + + saberent->touch = SaberGotHit; + + saberent->think = saberBackToOwner; + saberent->speed = 0; + saberent->genericValue5 = 0; + saberent->nextthink = level.time; + + saberent->r.contents = CONTENTS_LIGHTSABER; + + G_Sound( saberOwn, CHAN_BODY, G_SoundIndex( "sound/weapons/force/pull.wav" ) ); + if (saberOwn->client->saber[0].soundOn) + { + G_Sound( saberent, CHAN_BODY, saberOwn->client->saber[0].soundOn ); + } + if (saberOwn->client->saber[1].soundOn) + { + G_Sound( saberOwn, CHAN_BODY, saberOwn->client->saber[1].soundOn ); + } + + return; + } + + G_RunObject(saberent); + saberent->nextthink = level.time; +} + +void saberReactivate(gentity_t *saberent, gentity_t *saberOwner) +{ + saberent->s.saberInFlight = qtrue; + + saberent->s.apos.trType = TR_LINEAR; + saberent->s.apos.trDelta[0] = 0; + saberent->s.apos.trDelta[1] = 800; + saberent->s.apos.trDelta[2] = 0; + + saberent->s.pos.trType = TR_LINEAR; + saberent->s.eType = ET_GENERAL; + saberent->s.eFlags = 0; + + saberent->parent = saberOwner; + + saberent->genericValue5 = 0; + + SetSaberBoxSize(saberent); + + saberent->touch = thrownSaberTouch; + + saberent->s.weapon = WP_SABER; + + saberOwner->client->ps.saberEntityState = 1; + + trap_LinkEntity(saberent); +} + +#define SABER_RETRIEVE_DELAY 3000 //3 seconds for now. This will leave you nice and open if you lose your saber. + +void saberKnockDown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other) +{ + saberOwner->client->ps.saberEntityNum = 0; //still stored in client->saberStoredIndex + saberOwner->client->saberKnockedTime = level.time + SABER_RETRIEVE_DELAY; + + saberent->clipmask = MASK_SOLID; + saberent->r.contents = CONTENTS_TRIGGER;//0; + + VectorSet( saberent->r.mins, -3.0f, -3.0f, -1.5f ); + VectorSet( saberent->r.maxs, 3.0f, 3.0f, 1.5f ); + + saberent->s.apos.trType = TR_GRAVITY; + saberent->s.apos.trDelta[0] = Q_irand(200, 800); + saberent->s.apos.trDelta[1] = Q_irand(200, 800); + saberent->s.apos.trDelta[2] = Q_irand(200, 800); + saberent->s.apos.trTime = level.time-50; + + saberent->s.pos.trType = TR_GRAVITY; + saberent->s.pos.trTime = level.time-50; + saberent->flags |= FL_BOUNCE_HALF; + + WP_SaberAddG2Model( saberent, saberOwner->client->saber[0].model, saberOwner->client->saber[0].skin ); + + saberent->s.modelGhoul2 = 1; + saberent->s.g2radius = 20; + + saberent->s.eType = ET_MISSILE; + saberent->s.weapon = WP_SABER; + + saberent->speed = level.time + 4000; + + saberent->bounceCount = -5;//8; + + saberMoveBack(saberent, qtrue); + saberent->s.pos.trType = TR_GRAVITY; + + saberent->s.loopSound = 0; //kill this in case it was spinning. + saberent->s.loopIsSoundset = qfalse; + + saberent->r.svFlags &= ~(SVF_NOCLIENT); //make sure the client is getting updates on where it is and such. + + saberent->touch = SaberBounceSound; + saberent->think = DownedSaberThink; + saberent->nextthink = level.time; + + if (saberOwner != other) + { //if someone knocked it out of the air and it wasn't turned off, go in the direction they were facing. + if (other->inuse && other->client) + { + vec3_t otherFwd; + float deflectSpeed = 200; + + AngleVectors(other->client->ps.viewangles, otherFwd, 0, 0); + + saberent->s.pos.trDelta[0] = otherFwd[0]*deflectSpeed; + saberent->s.pos.trDelta[1] = otherFwd[1]*deflectSpeed; + saberent->s.pos.trDelta[2] = otherFwd[2]*deflectSpeed; + } + } + + trap_LinkEntity(saberent); + + if (saberOwner->client->saber[0].soundOff) + { + G_Sound( saberent, CHAN_BODY, saberOwner->client->saber[0].soundOff ); + } + + if (saberOwner->client->saber[1].soundOff && + saberOwner->client->saber[1].model[0]) + { + G_Sound( saberOwner, CHAN_BODY, saberOwner->client->saber[1].soundOff ); + } +} + +//sort of a silly macro I guess. But if I change anything in here I'll probably want it to be everywhere. +#define SABERINVALID (!saberent || !saberOwner || !other || !saberent->inuse || !saberOwner->inuse || !other->inuse || !saberOwner->client || !other->client || !saberOwner->client->ps.saberEntityNum || saberOwner->client->ps.saberLockTime > (level.time-100)) + +void WP_SaberRemoveG2Model( gentity_t *saberent ) +{ + if ( saberent->ghoul2 ) + { + trap_G2API_RemoveGhoul2Models( &saberent->ghoul2 ); + } +} + +void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin ) +{ + WP_SaberRemoveG2Model( saberent ); + if ( saberModel && saberModel[0] ) + { + saberent->s.modelindex = G_ModelIndex(saberModel); + } + else + { + saberent->s.modelindex = G_ModelIndex( "models/weapons2/saber/saber_w.glm" ); + } + //FIXME: use customSkin? + trap_G2API_InitGhoul2Model( &saberent->ghoul2, saberModel, saberent->s.modelindex, saberSkin, 0, 0, 0 ); +} + +//Make the saber go flying directly out of the owner's hand in the specified direction +qboolean saberKnockOutOfHand(gentity_t *saberent, gentity_t *saberOwner, vec3_t velocity) +{ + if (!saberent || !saberOwner || + !saberent->inuse || !saberOwner->inuse || + !saberOwner->client) + { + return qfalse; + } + + if (!saberOwner->client->ps.saberEntityNum) + { //already gone + return qfalse; + } + + if ((level.time - saberOwner->client->lastSaberStorageTime) > 50) + { //must have a reasonably updated saber base pos + return qfalse; + } + + if (saberOwner->client->ps.saberLockTime > (level.time-100)) + { + return qfalse; + } + if ( !saberOwner->client->saber[0].disarmable ) + { + return qfalse; + } + + saberOwner->client->ps.saberInFlight = qtrue; + saberOwner->client->ps.saberEntityState = 1; + + saberent->s.saberInFlight = qfalse;//qtrue; + + saberent->s.pos.trType = TR_LINEAR; + saberent->s.eType = ET_GENERAL; + saberent->s.eFlags = 0; + + WP_SaberAddG2Model( saberent, saberOwner->client->saber[0].model, saberOwner->client->saber[0].skin ); + + saberent->s.modelGhoul2 = 127; + + saberent->parent = saberOwner; + + saberent->damage = SABER_THROWN_HIT_DAMAGE; + saberent->methodOfDeath = MOD_SABER; + saberent->splashMethodOfDeath = MOD_SABER; + saberent->s.solid = 2; + saberent->r.contents = CONTENTS_LIGHTSABER; + + saberent->genericValue5 = 0; + + VectorSet( saberent->r.mins, -24.0f, -24.0f, -8.0f ); + VectorSet( saberent->r.maxs, 24.0f, 24.0f, 8.0f ); + + saberent->s.genericenemyindex = saberOwner->s.number+1024; + saberent->s.weapon = WP_SABER; + + saberent->genericValue5 = 0; + + G_SetOrigin(saberent, saberOwner->client->lastSaberBase_Always); //use this as opposed to the right hand bolt, + //because I don't want to risk reconstructing the skel again to get it here. And it isn't worth storing. + saberKnockDown(saberent, saberOwner, saberOwner); + VectorCopy(velocity, saberent->s.pos.trDelta); //override the velocity on the knocked away saber. + + return qtrue; +} + +//Called at the result of a circle lock duel - the loser gets his saber tossed away and is put into a reflected attack anim +qboolean saberCheckKnockdown_DuelLoss(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other) +{ + vec3_t dif; + float totalDistance = 1; + float distScale = 6.5f; + qboolean validMomentum = qtrue; + int disarmChance = 1; + + if (SABERINVALID) + { + return qfalse; + } + + VectorClear(dif); + + if (!other->client->olderIsValid || (level.time - other->client->lastSaberStorageTime) >= 200) + { //see if the spots are valid + validMomentum = qfalse; + } + + if (validMomentum) + { + //Get the difference + VectorSubtract(other->client->lastSaberBase_Always, other->client->olderSaberBase, dif); + totalDistance = VectorNormalize(dif); + + if (!totalDistance) + { //fine, try our own + if (!saberOwner->client->olderIsValid || (level.time - saberOwner->client->lastSaberStorageTime) >= 200) + { + validMomentum = qfalse; + } + + if (validMomentum) + { + VectorSubtract(saberOwner->client->lastSaberBase_Always, saberOwner->client->olderSaberBase, dif); + totalDistance = VectorNormalize(dif); + } + } + + if (validMomentum) + { + if (!totalDistance) + { //try the difference between the two blades + VectorSubtract(saberOwner->client->lastSaberBase_Always, other->client->lastSaberBase_Always, dif); + totalDistance = VectorNormalize(dif); + } + + if (totalDistance) + { //if we still have no difference somehow, just let it fall to the ground when the time comes. + if (totalDistance < 20) + { + totalDistance = 20; + } + VectorScale(dif, totalDistance*distScale, dif); + } + } + } + + saberOwner->client->ps.saberMove = LS_V1_BL; //rwwFIXMEFIXME: Ideally check which lock it was exactly and use the proper anim (same goes for the attacker) + saberOwner->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; + + if ( other && other->client ) + { + disarmChance += other->client->saber[0].disarmBonus; + if ( other->client->saber[1].model + && other->client->saber[1].model[0] + && !other->client->ps.saberHolstered ) + { + other->client->saber[1].disarmBonus; + } + } + if ( Q_irand( 0, disarmChance ) ) + { + return saberKnockOutOfHand(saberent, saberOwner, dif); + } + else + { + return qfalse; + } +} + +//Called when we want to try knocking the saber out of the owner's hand upon them going into a broken parry. +//Also called on reflected attacks. +qboolean saberCheckKnockdown_BrokenParry(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other) +{ + int myAttack; + int otherAttack; + qboolean doKnock = qfalse; + int disarmChance = 1; + + if (SABERINVALID) + { + return qfalse; + } + + //Neither gets an advantage based on attack state, when it comes to knocking + //saber out of hand. + myAttack = G_SaberAttackPower(saberOwner, qfalse); + otherAttack = G_SaberAttackPower(other, qfalse); + + if (!other->client->olderIsValid || (level.time - other->client->lastSaberStorageTime) >= 200) + { //if we don't know which way to throw the saber based on momentum between saber positions, just don't throw it + return qfalse; + } + + //only knock the saber out of the hand if they're in a stronger stance I suppose. Makes strong more advantageous. + if (otherAttack > myAttack+1 && Q_irand(1, 10) <= 7) + { //This would be, say, strong stance against light stance. + doKnock = qtrue; + } + else if (otherAttack > myAttack && Q_irand(1, 10) <= 3) + { //Strong vs. medium, medium vs. light + doKnock = qtrue; + } + + if (doKnock) + { + vec3_t dif; + float totalDistance; + float distScale = 6.5f; + + VectorSubtract(other->client->lastSaberBase_Always, other->client->olderSaberBase, dif); + totalDistance = VectorNormalize(dif); + + if (!totalDistance) + { //fine, try our own + if (!saberOwner->client->olderIsValid || (level.time - saberOwner->client->lastSaberStorageTime) >= 200) + { //if we don't know which way to throw the saber based on momentum between saber positions, just don't throw it + return qfalse; + } + + VectorSubtract(saberOwner->client->lastSaberBase_Always, saberOwner->client->olderSaberBase, dif); + totalDistance = VectorNormalize(dif); + } + + if (!totalDistance) + { //...forget it then. + return qfalse; + } + + if (totalDistance < 20) + { + totalDistance = 20; + } + VectorScale(dif, totalDistance*distScale, dif); + + if ( other && other->client ) + { + disarmChance += other->client->saber[0].disarmBonus; + if ( other->client->saber[1].model + && other->client->saber[1].model[0] + && !other->client->ps.saberHolstered ) + { + other->client->saber[1].disarmBonus; + } + } + if ( Q_irand( 0, disarmChance ) ) + { + return saberKnockOutOfHand(saberent, saberOwner, dif); + } + } + + return qfalse; +} + +#include "../namespace_begin.h" +qboolean BG_InExtraDefenseSaberMove( int move ); +#include "../namespace_end.h" + +//Called upon an enemy actually slashing into a thrown saber +qboolean saberCheckKnockdown_Smashed(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other, int damage) +{ + if (SABERINVALID) + { + return qfalse; + } + + if (!saberOwner->client->ps.saberInFlight) + { //can only do this if the saber is already actually in flight + return qfalse; + } + + if ( other + && other->inuse + && other->client + && BG_InExtraDefenseSaberMove( other->client->ps.saberMove ) ) + { //make sure the blow was strong enough + saberKnockDown(saberent, saberOwner, other); + return qtrue; + } + + if (damage > 10) + { //make sure the blow was strong enough + saberKnockDown(saberent, saberOwner, other); + return qtrue; + } + + return qfalse; +} + +//Called upon blocking a thrown saber. If the throw level compared to the blocker's defense level +//is inferior, or equal and a random factor is met, then the saber will be tossed to the ground. +qboolean saberCheckKnockdown_Thrown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other) +{ + int throwLevel = 0; + int defenLevel = 0; + qboolean tossIt = qfalse; + + if (SABERINVALID) + { + return qfalse; + } + + defenLevel = other->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]; + throwLevel = saberOwner->client->ps.fd.forcePowerLevel[FP_SABERTHROW]; + + if (defenLevel > throwLevel) + { + tossIt = qtrue; + } + else if (defenLevel == throwLevel && Q_irand(1, 10) <= 4) + { + tossIt = qtrue; + } + //otherwise don't + + if (tossIt) + { + saberKnockDown(saberent, saberOwner, other); + return qtrue; + } + + return qfalse; +} + +void saberBackToOwner(gentity_t *saberent) +{ + gentity_t *saberOwner = &g_entities[saberent->r.ownerNum]; + vec3_t dir; + float ownerLen; + + if (saberent->r.ownerNum == ENTITYNUM_NONE) + { + MakeDeadSaber(saberent); + + saberent->think = G_FreeEntity; + saberent->nextthink = level.time; + return; + } + + if (!saberOwner->inuse || + !saberOwner->client || + saberOwner->client->sess.sessionTeam == TEAM_SPECTATOR) + { + MakeDeadSaber(saberent); + + saberent->think = G_FreeEntity; + saberent->nextthink = level.time; + return; + } + + if (saberOwner->health < 1 || !saberOwner->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { //He's dead, just go back to our normal saber status + saberent->touch = SaberGotHit; + saberent->think = SaberUpdateSelf; + saberent->genericValue5 = 0; + saberent->nextthink = level.time; + + if (saberOwner->client && + saberOwner->client->saber[0].soundOff) + { + G_Sound(saberent, CHAN_AUTO, saberOwner->client->saber[0].soundOff); + } + MakeDeadSaber(saberent); + + saberent->r.svFlags |= (SVF_NOCLIENT); + saberent->r.contents = CONTENTS_LIGHTSABER; + SetSaberBoxSize(saberent); + saberent->s.loopSound = 0; + saberent->s.loopIsSoundset = qfalse; + WP_SaberRemoveG2Model( saberent ); + + saberOwner->client->ps.saberInFlight = qfalse; + saberOwner->client->ps.saberEntityState = 0; + saberOwner->client->ps.saberThrowDelay = level.time + 500; + saberOwner->client->ps.saberCanThrow = qfalse; + + return; + } + + //make sure this is set alright + assert(saberOwner->client->ps.saberEntityNum == saberent->s.number || + saberOwner->client->saberStoredIndex == saberent->s.number); + saberOwner->client->ps.saberEntityNum = saberent->s.number; + + saberent->r.contents = CONTENTS_LIGHTSABER; + + VectorSubtract(saberent->pos1, saberent->r.currentOrigin, dir); + + ownerLen = VectorLength(dir); + + if (saberent->speed < level.time) + { + float baseSpeed = 900; + + VectorNormalize(dir); + + saberMoveBack(saberent, qtrue); + VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase); + + if (saberOwner->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3) + { //allow players with high saber throw rank to control the return speed of the saber + baseSpeed = 900; + + saberent->speed = level.time;// + 200; + } + else + { + baseSpeed = 700; + saberent->speed = level.time + 50; + } + + //Gradually slow down as it approaches, so it looks smoother coming into the hand. + if (ownerLen < 64) + { + VectorScale(dir, baseSpeed-200, saberent->s.pos.trDelta ); + } + else if (ownerLen < 128) + { + VectorScale(dir, baseSpeed-150, saberent->s.pos.trDelta ); + } + else if (ownerLen < 256) + { + VectorScale(dir, baseSpeed-100, saberent->s.pos.trDelta ); + } + else + { + VectorScale(dir, baseSpeed, saberent->s.pos.trDelta ); + } + + saberent->s.pos.trTime = level.time; + } + + /* + if (ownerLen <= 512) + { + saberent->s.saberInFlight = qfalse; + saberent->s.loopSound = saberHumSound; + saberent->s.loopIsSoundset = qfalse; + } + */ + //I'm just doing this now. I don't really like the spin on the way back. And it does weird stuff with the new saber-knocked-away code. + if (saberOwner->client->ps.saberEntityNum == saberent->s.number) + { + if ( !saberOwner->client->saber[0].returnDamage + || saberOwner->client->ps.saberHolstered ) + { + saberent->s.saberInFlight = qfalse; + } + saberent->s.loopSound = saberOwner->client->saber[0].soundLoop; + saberent->s.loopIsSoundset = qfalse; + + if (ownerLen <= 32) + { + G_Sound( saberent, CHAN_AUTO, G_SoundIndex( "sound/weapons/saber/saber_catch.wav" ) ); + + saberOwner->client->ps.saberInFlight = qfalse; + saberOwner->client->ps.saberEntityState = 0; + saberOwner->client->ps.saberCanThrow = qfalse; + saberOwner->client->ps.saberThrowDelay = level.time + 300; + + saberent->touch = SaberGotHit; + + saberent->think = SaberUpdateSelf; + saberent->genericValue5 = 0; + saberent->nextthink = level.time + 50; + WP_SaberRemoveG2Model( saberent ); + + return; + } + + if (!saberent->s.saberInFlight) + { + saberCheckRadiusDamage(saberent, 1); + } + else + { + saberCheckRadiusDamage(saberent, 2); + } + + saberMoveBack(saberent, qtrue); + } + + saberent->nextthink = level.time; +} + +void saberFirstThrown(gentity_t *saberent); + +void thrownSaberTouch (gentity_t *saberent, gentity_t *other, trace_t *trace) +{ + gentity_t *hitEnt = other; + + if (other && other->s.number == saberent->r.ownerNum) + { + return; + } + VectorClear(saberent->s.pos.trDelta); + saberent->s.pos.trTime = level.time; + + saberent->s.apos.trType = TR_LINEAR; + saberent->s.apos.trDelta[0] = 0; + saberent->s.apos.trDelta[1] = 800; + saberent->s.apos.trDelta[2] = 0; + + VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase); + + saberent->think = saberBackToOwner; + saberent->nextthink = level.time; + + if (other && other->r.ownerNum < MAX_CLIENTS && + (other->r.contents & CONTENTS_LIGHTSABER) && + g_entities[other->r.ownerNum].client && + g_entities[other->r.ownerNum].inuse) + { + hitEnt = &g_entities[other->r.ownerNum]; + } + + //we'll skip the dist check, since we don't really care about that (we just hit it physically) + CheckThrownSaberDamaged(saberent, &g_entities[saberent->r.ownerNum], hitEnt, 256, 0, qtrue); + + saberent->speed = 0; +} + +#define SABER_MAX_THROW_DISTANCE 700 + +void saberFirstThrown(gentity_t *saberent) +{ + vec3_t vSub; + float vLen; + gentity_t *saberOwn = &g_entities[saberent->r.ownerNum]; + + if (saberent->r.ownerNum == ENTITYNUM_NONE) + { + MakeDeadSaber(saberent); + + saberent->think = G_FreeEntity; + saberent->nextthink = level.time; + return; + } + + if (!saberOwn || + !saberOwn->inuse || + !saberOwn->client || + saberOwn->client->sess.sessionTeam == TEAM_SPECTATOR) + { + MakeDeadSaber(saberent); + + saberent->think = G_FreeEntity; + saberent->nextthink = level.time; + return; + } + + if (saberOwn->health < 1 || !saberOwn->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { //He's dead, just go back to our normal saber status + saberent->touch = SaberGotHit; + saberent->think = SaberUpdateSelf; + saberent->genericValue5 = 0; + saberent->nextthink = level.time; + + if (saberOwn->client && + saberOwn->client->saber[0].soundOff) + { + G_Sound(saberent, CHAN_AUTO, saberOwn->client->saber[0].soundOff); + } + MakeDeadSaber(saberent); + + saberent->r.svFlags |= (SVF_NOCLIENT); + saberent->r.contents = CONTENTS_LIGHTSABER; + SetSaberBoxSize(saberent); + saberent->s.loopSound = 0; + saberent->s.loopIsSoundset = qfalse; + WP_SaberRemoveG2Model( saberent ); + + saberOwn->client->ps.saberInFlight = qfalse; + saberOwn->client->ps.saberEntityState = 0; + saberOwn->client->ps.saberThrowDelay = level.time + 500; + saberOwn->client->ps.saberCanThrow = qfalse; + + return; + } + + if ((level.time - saberOwn->client->ps.saberDidThrowTime) > 500) + { + if (!(saberOwn->client->buttons & BUTTON_ALT_ATTACK)) + { //If owner releases altattack 500ms or later after throwing saber, it autoreturns + thrownSaberTouch(saberent, saberent, NULL); + goto runMin; + } + else if ((level.time - saberOwn->client->ps.saberDidThrowTime) > 6000) + { //if it's out longer than 6 seconds, return it + thrownSaberTouch(saberent, saberent, NULL); + goto runMin; + } + } + + if (BG_HasYsalamiri(g_gametype.integer, &saberOwn->client->ps)) + { + thrownSaberTouch(saberent, saberent, NULL); + goto runMin; + } + + if (!BG_CanUseFPNow(g_gametype.integer, &saberOwn->client->ps, level.time, FP_SABERTHROW)) + { + thrownSaberTouch(saberent, saberent, NULL); + goto runMin; + } + + VectorSubtract(saberOwn->client->ps.origin, saberent->r.currentOrigin, vSub); + vLen = VectorLength(vSub); + + if (vLen >= (SABER_MAX_THROW_DISTANCE*saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW])) + { + thrownSaberTouch(saberent, saberent, NULL); + goto runMin; + } + + if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_2 && + saberent->speed < level.time) + { //if owner is rank 3 in saber throwing, the saber goes where he points + vec3_t fwd, traceFrom, traceTo, dir; + trace_t tr; + + AngleVectors(saberOwn->client->ps.viewangles, fwd, 0, 0); + + VectorCopy(saberOwn->client->ps.origin, traceFrom); + traceFrom[2] += saberOwn->client->ps.viewheight; + + VectorCopy(traceFrom, traceTo); + traceTo[0] += fwd[0]*4096; + traceTo[1] += fwd[1]*4096; + traceTo[2] += fwd[2]*4096; + + saberMoveBack(saberent, qfalse); + VectorCopy(saberent->r.currentOrigin, saberent->s.pos.trBase); + + if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3) + { //if highest saber throw rank, we can direct the saber toward players directly by looking at them + trap_Trace(&tr, traceFrom, NULL, NULL, traceTo, saberOwn->s.number, MASK_PLAYERSOLID); + } + else + { + trap_Trace(&tr, traceFrom, NULL, NULL, traceTo, saberOwn->s.number, MASK_SOLID); + } + + VectorSubtract(tr.endpos, saberent->r.currentOrigin, dir); + + VectorNormalize(dir); + + VectorScale(dir, 500, saberent->s.pos.trDelta ); + saberent->s.pos.trTime = level.time; + + if (saberOwn->client->ps.fd.forcePowerLevel[FP_SABERTHROW] >= FORCE_LEVEL_3) + { //we'll treat them to a quicker update rate if their throw rank is high enough + saberent->speed = level.time + 100; + } + else + { + saberent->speed = level.time + 400; + } + } + +runMin: + + saberCheckRadiusDamage(saberent, 0); + G_RunObject(saberent); +} + +void UpdateClientRenderBolts(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles) +{ + mdxaBone_t boltMatrix; + renderInfo_t *ri = &self->client->renderInfo; + + if (!self->ghoul2) + { + VectorCopy(self->client->ps.origin, ri->headPoint); + VectorCopy(self->client->ps.origin, ri->handRPoint); + VectorCopy(self->client->ps.origin, ri->handLPoint); + VectorCopy(self->client->ps.origin, ri->torsoPoint); + VectorCopy(self->client->ps.origin, ri->crotchPoint); + VectorCopy(self->client->ps.origin, ri->footRPoint); + VectorCopy(self->client->ps.origin, ri->footLPoint); + } + else + { + //head + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->headBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->headPoint[0] = boltMatrix.matrix[0][3]; + ri->headPoint[1] = boltMatrix.matrix[1][3]; + ri->headPoint[2] = boltMatrix.matrix[2][3]; + + //right hand + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->handRPoint[0] = boltMatrix.matrix[0][3]; + ri->handRPoint[1] = boltMatrix.matrix[1][3]; + ri->handRPoint[2] = boltMatrix.matrix[2][3]; + + //left hand + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->handLPoint[0] = boltMatrix.matrix[0][3]; + ri->handLPoint[1] = boltMatrix.matrix[1][3]; + ri->handLPoint[2] = boltMatrix.matrix[2][3]; + + //chest + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->torsoBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->torsoPoint[0] = boltMatrix.matrix[0][3]; + ri->torsoPoint[1] = boltMatrix.matrix[1][3]; + ri->torsoPoint[2] = boltMatrix.matrix[2][3]; + + //crotch + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->crotchBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->crotchPoint[0] = boltMatrix.matrix[0][3]; + ri->crotchPoint[1] = boltMatrix.matrix[1][3]; + ri->crotchPoint[2] = boltMatrix.matrix[2][3]; + + //right foot + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->footRPoint[0] = boltMatrix.matrix[0][3]; + ri->footRPoint[1] = boltMatrix.matrix[1][3]; + ri->footRPoint[2] = boltMatrix.matrix[2][3]; + + //left foot + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->footLPoint[0] = boltMatrix.matrix[0][3]; + ri->footLPoint[1] = boltMatrix.matrix[1][3]; + ri->footLPoint[2] = boltMatrix.matrix[2][3]; + } + + self->client->renderInfo.boltValidityTime = level.time; +} + +void UpdateClientRenderinfo(gentity_t *self, vec3_t renderOrigin, vec3_t renderAngles) +{ + renderInfo_t *ri = &self->client->renderInfo; + if ( ri->mPCalcTime < level.time ) + { + //We're just going to give rough estimates on most of this stuff, + //it's not like most of it matters. + + #if 0 //#if 0'd since it's a waste setting all this to 0 each frame. + //Should you wish to make any of this valid then feel free to do so. + ri->headYawRangeLeft = ri->headYawRangeRight = ri->headPitchRangeUp = ri->headPitchRangeDown = 0; + ri->torsoYawRangeLeft = ri->torsoYawRangeRight = ri->torsoPitchRangeUp = ri->torsoPitchRangeDown = 0; + + ri->torsoFpsMod = ri->legsFpsMod = 0; + + VectorClear(ri->customRGB); + ri->customAlpha = 0; + ri->renderFlags = 0; + ri->lockYaw = 0; + + VectorClear(ri->headAngles); + VectorClear(ri->torsoAngles); + + //VectorClear(ri->eyeAngles); + + ri->legsYaw = 0; + #endif + + if (self->ghoul2 && + self->ghoul2 != ri->lastG2) + { //the g2 instance changed, so update all the bolts. + //rwwFIXMEFIXME: Base on skeleton used? Assuming humanoid currently. + ri->lastG2 = self->ghoul2; + + if (self->localAnimIndex <= 1) + { + ri->headBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*head_eyes"); + ri->handRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_hand"); + ri->handLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_hand"); + ri->torsoBolt = trap_G2API_AddBolt(self->ghoul2, 0, "thoracic"); + ri->crotchBolt = trap_G2API_AddBolt(self->ghoul2, 0, "pelvis"); + ri->footRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_leg_foot"); + ri->footLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_leg_foot"); + ri->motionBolt = trap_G2API_AddBolt(self->ghoul2, 0, "Motion"); + } + else + { + ri->headBolt = -1; + ri->handRBolt = -1; + ri->handLBolt = -1; + ri->torsoBolt = -1; + ri->crotchBolt = -1; + ri->footRBolt = -1; + ri->footLBolt = -1; + ri->motionBolt = -1; + } + + ri->lastG2 = self->ghoul2; + } + + VectorCopy( self->client->ps.viewangles, self->client->renderInfo.eyeAngles ); + + //we'll just say the legs/torso are whatever the first frame of our current anim is. + ri->torsoFrame = bgAllAnims[self->localAnimIndex].anims[self->client->ps.torsoAnim].firstFrame; + ri->legsFrame = bgAllAnims[self->localAnimIndex].anims[self->client->ps.legsAnim].firstFrame; + if (g_debugServerSkel.integer) + { //Alright, I was doing this, but it's just too slow to do every frame. + //From now on if we want this data to be valid we're going to have to make a verify call for it before + //accessing it. I'm only doing this now if we want to debug the server skel by drawing lines from bolt + //positions every frame. + mdxaBone_t boltMatrix; + + if (!self->ghoul2) + { + VectorCopy(self->client->ps.origin, ri->headPoint); + VectorCopy(self->client->ps.origin, ri->handRPoint); + VectorCopy(self->client->ps.origin, ri->handLPoint); + VectorCopy(self->client->ps.origin, ri->torsoPoint); + VectorCopy(self->client->ps.origin, ri->crotchPoint); + VectorCopy(self->client->ps.origin, ri->footRPoint); + VectorCopy(self->client->ps.origin, ri->footLPoint); + } + else + { + //head + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->headBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->headPoint[0] = boltMatrix.matrix[0][3]; + ri->headPoint[1] = boltMatrix.matrix[1][3]; + ri->headPoint[2] = boltMatrix.matrix[2][3]; + + //right hand + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->handRPoint[0] = boltMatrix.matrix[0][3]; + ri->handRPoint[1] = boltMatrix.matrix[1][3]; + ri->handRPoint[2] = boltMatrix.matrix[2][3]; + + //left hand + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->handLPoint[0] = boltMatrix.matrix[0][3]; + ri->handLPoint[1] = boltMatrix.matrix[1][3]; + ri->handLPoint[2] = boltMatrix.matrix[2][3]; + + //chest + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->torsoBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->torsoPoint[0] = boltMatrix.matrix[0][3]; + ri->torsoPoint[1] = boltMatrix.matrix[1][3]; + ri->torsoPoint[2] = boltMatrix.matrix[2][3]; + + //crotch + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->crotchBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->crotchPoint[0] = boltMatrix.matrix[0][3]; + ri->crotchPoint[1] = boltMatrix.matrix[1][3]; + ri->crotchPoint[2] = boltMatrix.matrix[2][3]; + + //right foot + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footRBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->footRPoint[0] = boltMatrix.matrix[0][3]; + ri->footRPoint[1] = boltMatrix.matrix[1][3]; + ri->footRPoint[2] = boltMatrix.matrix[2][3]; + + //left foot + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->footLBolt, &boltMatrix, renderAngles, renderOrigin, level.time, NULL, self->modelScale); + ri->footLPoint[0] = boltMatrix.matrix[0][3]; + ri->footLPoint[1] = boltMatrix.matrix[1][3]; + ri->footLPoint[2] = boltMatrix.matrix[2][3]; + } + + //Now draw the skel for debug + G_TestLine(ri->headPoint, ri->torsoPoint, 0x000000ff, 50); + G_TestLine(ri->torsoPoint, ri->handRPoint, 0x000000ff, 50); + G_TestLine(ri->torsoPoint, ri->handLPoint, 0x000000ff, 50); + G_TestLine(ri->torsoPoint, ri->crotchPoint, 0x000000ff, 50); + G_TestLine(ri->crotchPoint, ri->footRPoint, 0x000000ff, 50); + G_TestLine(ri->crotchPoint, ri->footLPoint, 0x000000ff, 50); + } + + //muzzle point calc (we are going to be cheap here) + VectorCopy(ri->muzzlePoint, ri->muzzlePointOld); + VectorCopy(self->client->ps.origin, ri->muzzlePoint); + VectorCopy(ri->muzzleDir, ri->muzzleDirOld); + AngleVectors(self->client->ps.viewangles, ri->muzzleDir, 0, 0); + ri->mPCalcTime = level.time; + + VectorCopy(self->client->ps.origin, ri->eyePoint); + ri->eyePoint[2] += self->client->ps.viewheight; + } +} + +#define STAFF_KICK_RANGE 16 +extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ); //NPC_utils.c + +extern qboolean BG_InKnockDown( int anim ); +static qboolean G_KickDownable(gentity_t *ent) +{ + if (!d_saberKickTweak.integer) + { + return qtrue; + } + + if (!ent || !ent->inuse || !ent->client) + { + return qfalse; + } + + if (BG_InKnockDown(ent->client->ps.legsAnim) || + BG_InKnockDown(ent->client->ps.torsoAnim)) + { + return qfalse; + } + + if (ent->client->ps.weaponTime <= 0 && + ent->client->ps.weapon == WP_SABER && + ent->client->ps.groundEntityNum != ENTITYNUM_NONE) + { + return qfalse; + } + + return qtrue; +} + +static void G_TossTheMofo(gentity_t *ent, vec3_t tossDir, float tossStr) +{ + if (!ent->inuse || !ent->client) + { //no good + return; + } + + if (ent->s.eType == ET_NPC && ent->s.NPC_class == CLASS_VEHICLE) + { //no, silly + return; + } + + VectorMA(ent->client->ps.velocity, tossStr, tossDir, ent->client->ps.velocity); + ent->client->ps.velocity[2] = 200; + if (ent->health > 0 && ent->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN && + BG_KnockDownable(&ent->client->ps) && + G_KickDownable(ent)) + { //if they are alive, knock them down I suppose + ent->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + ent->client->ps.forceHandExtendTime = level.time + 700; + ent->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + //ent->client->ps.quickerGetup = qtrue; + } +} + +static gentity_t *G_KickTrace( gentity_t *ent, vec3_t kickDir, float kickDist, vec3_t kickEnd, int kickDamage, float kickPush ) +{ + vec3_t traceOrg, traceEnd, kickMins, kickMaxs; + trace_t trace; + gentity_t *hitEnt = NULL; + VectorSet(kickMins, -2.0f, -2.0f, -2.0f); + VectorSet(kickMaxs, 2.0f, 2.0f, 2.0f); + //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->r.currentOrigin[0], ent->r.currentOrigin[1], kickEnd[2] ); + VectorCopy( kickEnd, traceEnd ); + } + else + {//extrude + VectorSet( traceOrg, ent->r.currentOrigin[0], ent->r.currentOrigin[1], ent->r.currentOrigin[2]+ent->r.maxs[2]*0.5f ); + VectorMA( traceOrg, kickDist, kickDir, traceEnd ); + } + + if (d_saberKickTweak.integer) + { + trap_G2Trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer ); + } + else + { + trap_Trace( &trace, traceOrg, kickMins, kickMaxs, traceEnd, ent->s.number, MASK_SHOT ); + } + + //G_TestLine(traceOrg, traceEnd, 0x0000ff, 5000); + if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid ) + { + if (ent->client->jediKickTime > level.time) + { + if (trace.entityNum == ent->client->jediKickIndex) + { //we are hitting the same ent we last hit in this same anim, don't hit it again + return NULL; + } + } + ent->client->jediKickIndex = trace.entityNum; + ent->client->jediKickTime = level.time + ent->client->ps.legsTimer; + + hitEnt = &g_entities[trace.entityNum]; + //FIXME: regardless of what we hit, do kick hit sound and impact effect + //G_PlayEffect( "misc/kickHit", trace.endpos, trace.plane.normal ); + if ( ent->client->ps.torsoAnim == BOTH_A7_HILT ) + { + G_Sound( ent, CHAN_AUTO, G_SoundIndex( "sound/movers/objects/saber_slam" ) ); + } + else + { + G_Sound( ent, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + } + if ( hitEnt->inuse ) + {//we hit an entity + //FIXME: don't hit same ent more than once per kick + if ( hitEnt->takedamage ) + {//hurt it + if (hitEnt->client) + { + hitEnt->client->ps.otherKiller = ent->s.number; + hitEnt->client->ps.otherKillerDebounceTime = level.time + 10000; + hitEnt->client->ps.otherKillerTime = level.time + 10000; + } + + if (d_saberKickTweak.integer) + { + G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage*0.2f, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + else + { + G_Damage( hitEnt, ent, ent, kickDir, trace.endpos, kickDamage, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + } + if ( hitEnt->client + && !(hitEnt->client->ps.pm_flags&PMF_TIME_KNOCKBACK) //not already flying through air? Intended to stop multiple hits, but... + && G_CanBeEnemy(ent, hitEnt) ) + {//FIXME: this should not always work + 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??? + // G_Throw( hitEnt, kickDir, kickPush*4 ); + //see if we should play a better looking death on them + // G_ThrownDeathAnimForDeathAnim( hitEnt, trace.endpos ); + G_TossTheMofo(hitEnt, kickDir, kickPush*4.0f); + } + else + { + /* + G_Throw( hitEnt, kickDir, kickPush ); + if ( kickPush >= 75.0f && !Q_irand( 0, 2 ) ) + { + G_Knockdown( hitEnt, ent, kickDir, 300, qtrue ); + } + else + { + G_Knockdown( hitEnt, ent, kickDir, kickPush, qtrue ); + } + */ + if ( kickPush >= 75.0f && !Q_irand( 0, 2 ) ) + { + G_TossTheMofo(hitEnt, kickDir, 300.0f); + } + else + { + G_TossTheMofo(hitEnt, kickDir, kickPush); + } + } + } + } + } + return (hitEnt); +} + +static void G_KickSomeMofos(gentity_t *ent) +{ + vec3_t kickDir, kickEnd, fwdAngs; + float animLength = BG_AnimLength( ent->localAnimIndex, (animNumber_t)ent->client->ps.legsAnim ); + float elapsedTime = (float)(animLength-ent->client->ps.legsTimer); + float remainingTime = (animLength-elapsedTime); + float kickDist = (ent->r.maxs[0]*1.5f)+STAFF_KICK_RANGE+8.0f;//fudge factor of 8 + int kickDamage = Q_irand(10, 15);//Q_irand( 3, 8 ); //since it can only hit a guy once now + int kickPush = flrand( 50.0f, 100.0f ); + qboolean doKick = qfalse; + renderInfo_t *ri = &ent->client->renderInfo; + + VectorSet(kickDir, 0.0f, 0.0f, 0.0f); + VectorSet(kickEnd, 0.0f, 0.0f, 0.0f); + VectorSet(fwdAngs, 0.0f, ent->client->ps.viewangles[YAW], 0.0f); + + //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 ( ri->handRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->handRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->client->ps.origin, 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_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//front + doKick = qtrue; + if ( ri->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->client->ps.origin, kickDir ); + kickDir[2] = 0;//ah, flatten it, I guess... + VectorNormalize( kickDir ); + } + else + {//guess + AngleVectors( fwdAngs, kickDir, NULL, NULL ); + } + } + break; + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + if ( elapsedTime >= 100 && remainingTime >= 250 ) + {//air + doKick = qtrue; + if ( ri->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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: + //FIXME: push forward? + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//front + doKick = qtrue; + if ( ri->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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: + //FIXME: push back? + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//back + doKick = qtrue; + if ( ri->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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: + //FIXME: push right? + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//right + doKick = qtrue; + if ( ri->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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: + //FIXME: push left? + if ( elapsedTime >= 250 && remainingTime >= 250 ) + {//left + doKick = qtrue; + if ( ri->footLBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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 = flrand( 75.0f, 125.0f ); + if ( ri->footRBolt != -1 ) + {//actually trace to a bolt + if ( elapsedTime >= 550 + && elapsedTime <= 1050 ) + { + doKick = qtrue; + G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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 = flrand( 75.0f, 125.0f ); + kickDist += 20.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 ( ri->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, ri->footRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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: + kickPush = flrand( 75.0f, 125.0f ); + kickDist += 10.0f; + + //ok, I'm tracing constantly on these things, they NEVER hit otherwise (in MP at least) + + //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 ) + if (level.framenum&1) + {//right + doKick = qtrue; + if ( ri->footRBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->footRBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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 ) + else + {//left + doKick = qtrue; + if ( ri->footLBolt != -1 ) + {//actually trace to a bolt + G_GetBoltPosition( ent, ri->footLBolt, kickEnd, 0 ); + VectorSubtract( kickEnd, ent->r.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 ); + G_KickTrace( ent, kickDir, kickDist, NULL, kickDamage, kickPush ); + } +} + +static GAME_INLINE qboolean G_PrettyCloseIGuess(float a, float b, float tolerance) +{ + if ((a-b) < tolerance && + (a-b) > -tolerance) + { + return qtrue; + } + + return qfalse; +} + +static void G_GrabSomeMofos(gentity_t *self) +{ + renderInfo_t *ri = &self->client->renderInfo; + mdxaBone_t boltMatrix; + vec3_t flatAng; + vec3_t pos; + vec3_t grabMins, grabMaxs; + trace_t trace; + + if (!self->ghoul2 || ri->handRBolt == -1) + { //no good + return; + } + + VectorSet(flatAng, 0.0f, self->client->ps.viewangles[1], 0.0f); + trap_G2API_GetBoltMatrix(self->ghoul2, 0, ri->handRBolt, &boltMatrix, flatAng, self->client->ps.origin, + level.time, NULL, self->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pos); + + VectorSet(grabMins, -4.0f, -4.0f, -4.0f); + VectorSet(grabMaxs, 4.0f, 4.0f, 4.0f); + + //trace from my origin to my hand, if we hit anyone then get 'em + trap_G2Trace( &trace, self->client->ps.origin, grabMins, grabMaxs, pos, self->s.number, MASK_SHOT, G2TRFLAG_DOGHOULTRACE|G2TRFLAG_GETSURFINDEX|G2TRFLAG_THICK|G2TRFLAG_HITCORPSES, g_g2TraceLod.integer ); + + if (trace.fraction != 1.0f && + trace.entityNum < ENTITYNUM_WORLD) + { + gentity_t *grabbed = &g_entities[trace.entityNum]; + + if (grabbed->inuse && (grabbed->s.eType == ET_PLAYER || grabbed->s.eType == ET_NPC) && + grabbed->client && grabbed->health > 0 && + G_CanBeEnemy(self, grabbed) && + G_PrettyCloseIGuess(grabbed->client->ps.origin[2], self->client->ps.origin[2], 4.0f) && + (!BG_InGrappleMove(grabbed->client->ps.torsoAnim) || grabbed->client->ps.torsoAnim == BOTH_KYLE_GRAB) && + (!BG_InGrappleMove(grabbed->client->ps.legsAnim) || grabbed->client->ps.legsAnim == BOTH_KYLE_GRAB)) + { //grabbed an active player/npc + int tortureAnim = -1; + int correspondingAnim = -1; + + if (self->client->pers.cmd.forwardmove > 0) + { //punch grab + tortureAnim = BOTH_KYLE_PA_1; + correspondingAnim = BOTH_PLAYER_PA_1; + } + else if (self->client->pers.cmd.forwardmove < 0) + { //knee-throw + tortureAnim = BOTH_KYLE_PA_2; + correspondingAnim = BOTH_PLAYER_PA_2; + } + + if (tortureAnim == -1 || correspondingAnim == -1) + { + if (self->client->ps.torsoTimer < 300 && !self->client->grappleState) + { //you failed to grab anyone, play the "failed to grab" anim + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (self->client->ps.torsoAnim == BOTH_KYLE_MISS) + { //providing the anim set succeeded.. + self->client->ps.weaponTime = self->client->ps.torsoTimer; + } + } + return; + } + + self->client->grappleIndex = grabbed->s.number; + self->client->grappleState = 1; + + grabbed->client->grappleIndex = self->s.number; + grabbed->client->grappleState = 20; + + //time to crack some heads + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, tortureAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (self->client->ps.torsoAnim == tortureAnim) + { //providing the anim set succeeded.. + self->client->ps.weaponTime = self->client->ps.torsoTimer; + } + + G_SetAnim(grabbed, &grabbed->client->pers.cmd, SETANIM_BOTH, correspondingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (grabbed->client->ps.torsoAnim == correspondingAnim) + { //providing the anim set succeeded.. + if (grabbed->client->ps.weapon == WP_SABER) + { //turn it off + if (!grabbed->client->ps.saberHolstered) + { + grabbed->client->ps.saberHolstered = 2; + if (grabbed->client->saber[0].soundOff) + { + G_Sound(grabbed, CHAN_AUTO, grabbed->client->saber[0].soundOff); + } + if (grabbed->client->saber[1].soundOff && + grabbed->client->saber[1].model[0]) + { + G_Sound(grabbed, CHAN_AUTO, grabbed->client->saber[1].soundOff); + } + } + } + if (grabbed->client->ps.torsoTimer < self->client->ps.torsoTimer) + { //make sure they stay in the anim at least as long as the grabber + grabbed->client->ps.torsoTimer = self->client->ps.torsoTimer; + } + grabbed->client->ps.weaponTime = grabbed->client->ps.torsoTimer; + } + } + } + + if (self->client->ps.torsoTimer < 300 && !self->client->grappleState) + { //you failed to grab anyone, play the "failed to grab" anim + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (self->client->ps.torsoAnim == BOTH_KYLE_MISS) + { //providing the anim set succeeded.. + self->client->ps.weaponTime = self->client->ps.torsoTimer; + } + } +} + +void WP_SaberPositionUpdate( gentity_t *self, usercmd_t *ucmd ) +{ //rww - keep the saber position as updated as possible on the server so that we can try to do realistic-looking contact stuff + //Note that this function also does the majority of working in maintaining the server g2 client instance (updating angles/anims/etc) + gentity_t *mySaber = NULL; + mdxaBone_t boltMatrix; + vec3_t properAngles, properOrigin; + vec3_t boltAngles, boltOrigin; + vec3_t end; + vec3_t legAxis[3]; + vec3_t addVel; + vec3_t rawAngles; + float fVSpeed = 0; + int returnAfterUpdate = 0; + float animSpeedScale = 1.0f; + int saberNum; + qboolean clientOverride; + gentity_t *vehEnt = NULL; + int rSaberNum = 0; + int rBladeNum = 0; + +#ifdef _DEBUG + if (g_disableServerG2.integer) + { + return; + } +#endif + + if (self && self->inuse && self->client) + { + if (self->client->saberCycleQueue) + { + self->client->ps.fd.saberDrawAnimLevel = self->client->saberCycleQueue; + } + else + { + self->client->ps.fd.saberDrawAnimLevel = self->client->ps.fd.saberAnimLevel; + } + } + + if (self && + self->inuse && + self->client && + self->client->saberCycleQueue && + (self->client->ps.weaponTime <= 0 || self->health < 1)) + { //we cycled attack levels while we were busy, so update now that we aren't (even if that means we're dead) + self->client->ps.fd.saberAnimLevel = self->client->saberCycleQueue; + self->client->saberCycleQueue = 0; + } + + if (!self || + !self->inuse || + !self->client || + !self->ghoul2 || + !g2SaberInstance) + { + return; + } + + if (BG_KickingAnim(self->client->ps.legsAnim)) + { //do some kick traces and stuff if we're in the appropriate anim + G_KickSomeMofos(self); + } + else if (self->client->ps.torsoAnim == BOTH_KYLE_GRAB) + { //try to grab someone + G_GrabSomeMofos(self); + } + else if (self->client->grappleState) + { + gentity_t *grappler = &g_entities[self->client->grappleIndex]; + + if (!grappler->inuse || !grappler->client || grappler->client->grappleIndex != self->s.number || + !BG_InGrappleMove(grappler->client->ps.torsoAnim) || !BG_InGrappleMove(grappler->client->ps.legsAnim) || + !BG_InGrappleMove(self->client->ps.torsoAnim) || !BG_InGrappleMove(self->client->ps.legsAnim) || + !self->client->grappleState || !grappler->client->grappleState || + grappler->health < 1 || self->health < 1 || + !G_PrettyCloseIGuess(self->client->ps.origin[2], grappler->client->ps.origin[2], 4.0f)) + { + self->client->grappleState = 0; + if ((BG_InGrappleMove(self->client->ps.torsoAnim) && self->client->ps.torsoTimer > 100) || + (BG_InGrappleMove(self->client->ps.legsAnim) && self->client->ps.legsTimer > 100)) + { //if they're pretty far from finishing the anim then shove them into another anim + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (self->client->ps.torsoAnim == BOTH_KYLE_MISS) + { //providing the anim set succeeded.. + self->client->ps.weaponTime = self->client->ps.torsoTimer; + } + } + } + else + { + vec3_t grapAng; + + VectorSubtract(grappler->client->ps.origin, self->client->ps.origin, grapAng); + + if (VectorLength(grapAng) > 64.0f) + { //too far away, break it off + if ((BG_InGrappleMove(self->client->ps.torsoAnim) && self->client->ps.torsoTimer > 100) || + (BG_InGrappleMove(self->client->ps.legsAnim) && self->client->ps.legsTimer > 100)) + { + self->client->grappleState = 0; + + G_SetAnim(self, &self->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_MISS, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (self->client->ps.torsoAnim == BOTH_KYLE_MISS) + { //providing the anim set succeeded.. + self->client->ps.weaponTime = self->client->ps.torsoTimer; + } + } + } + else + { + vectoangles(grapAng, grapAng); + SetClientViewAngle(self, grapAng); + + if (self->client->grappleState >= 20) + { //grapplee + //try to position myself at the correct distance from my grappler + float idealDist; + vec3_t gFwd, idealSpot; + trace_t trace; + + if (grappler->client->ps.torsoAnim == BOTH_KYLE_PA_1) + { //grab punch + idealDist = 46.0f; + } + else + { //knee-throw + idealDist = 34.0f; + } + + AngleVectors(grappler->client->ps.viewangles, gFwd, 0, 0); + VectorMA(grappler->client->ps.origin, idealDist, gFwd, idealSpot); + + trap_Trace(&trace, self->client->ps.origin, self->r.mins, self->r.maxs, idealSpot, self->s.number, self->clipmask); + if (!trace.startsolid && !trace.allsolid && trace.fraction == 1.0f) + { //go there + G_SetOrigin(self, idealSpot); + VectorCopy(idealSpot, self->client->ps.origin); + } + } + else if (self->client->grappleState >= 1) + { //grappler + if (grappler->client->ps.weapon == WP_SABER) + { //make sure their saber is shut off + if (!grappler->client->ps.saberHolstered) + { + grappler->client->ps.saberHolstered = 2; + if (grappler->client->saber[0].soundOff) + { + G_Sound(grappler, CHAN_AUTO, grappler->client->saber[0].soundOff); + } + if (grappler->client->saber[1].soundOff && + grappler->client->saber[1].model[0]) + { + G_Sound(grappler, CHAN_AUTO, grappler->client->saber[1].soundOff); + } + } + } + + //check for smashy events + if (self->client->ps.torsoAnim == BOTH_KYLE_PA_1) + { //grab punch + if (self->client->grappleState == 1) + { //smack + if (self->client->ps.torsoTimer < 3400) + { + int grapplerAnim = grappler->client->ps.torsoAnim; + int grapplerTime = grappler->client->ps.torsoTimer; + + G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE); + //G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + + //it might try to put them into a pain anim or something, so override it back again + if (grappler->health > 0) + { + grappler->client->ps.torsoAnim = grapplerAnim; + grappler->client->ps.torsoTimer = grapplerTime; + grappler->client->ps.legsAnim = grapplerAnim; + grappler->client->ps.legsTimer = grapplerTime; + grappler->client->ps.weaponTime = grapplerTime; + } + self->client->grappleState++; + } + } + else if (self->client->grappleState == 2) + { //smack! + if (self->client->ps.torsoTimer < 2550) + { + int grapplerAnim = grappler->client->ps.torsoAnim; + int grapplerTime = grappler->client->ps.torsoTimer; + + G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE); + //G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + + //it might try to put them into a pain anim or something, so override it back again + if (grappler->health > 0) + { + grappler->client->ps.torsoAnim = grapplerAnim; + grappler->client->ps.torsoTimer = grapplerTime; + grappler->client->ps.legsAnim = grapplerAnim; + grappler->client->ps.legsTimer = grapplerTime; + grappler->client->ps.weaponTime = grapplerTime; + } + self->client->grappleState++; + } + } + else + { //SMACK! + if (self->client->ps.torsoTimer < 1300) + { + vec3_t tossDir; + + G_Damage(grappler, self, self, NULL, self->client->ps.origin, 30, 0, MOD_MELEE); + //G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + + self->client->grappleState = 0; + + VectorSubtract(grappler->client->ps.origin, self->client->ps.origin, tossDir); + VectorNormalize(tossDir); + VectorScale(tossDir, 500.0f, tossDir); + tossDir[2] = 200.0f; + + VectorAdd(grappler->client->ps.velocity, tossDir, grappler->client->ps.velocity); + + if (grappler->health > 0) + { //if still alive knock them down + grappler->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + grappler->client->ps.forceHandExtendTime = level.time + 1300; + } + } + } + } + else if (self->client->ps.torsoAnim == BOTH_KYLE_PA_2) + { //knee throw + if (self->client->grappleState == 1) + { //knee to the face + if (self->client->ps.torsoTimer < 3200) + { + int grapplerAnim = grappler->client->ps.torsoAnim; + int grapplerTime = grappler->client->ps.torsoTimer; + + G_Damage(grappler, self, self, NULL, self->client->ps.origin, 20, 0, MOD_MELEE); + //G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + + //it might try to put them into a pain anim or something, so override it back again + if (grappler->health > 0) + { + grappler->client->ps.torsoAnim = grapplerAnim; + grappler->client->ps.torsoTimer = grapplerTime; + grappler->client->ps.legsAnim = grapplerAnim; + grappler->client->ps.legsTimer = grapplerTime; + grappler->client->ps.weaponTime = grapplerTime; + } + self->client->grappleState++; + } + } + else if (self->client->grappleState == 2) + { //smashed on the ground + if (self->client->ps.torsoTimer < 2000) + { + //G_Damage(grappler, self, self, NULL, self->client->ps.origin, 10, 0, MOD_MELEE); + //don't do damage on this one, it would look very freaky if they died + G_EntitySound( grappler, CHAN_VOICE, G_SoundIndex("*pain100.wav") ); + //G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + self->client->grappleState++; + } + } + else + { //and another smash + if (self->client->ps.torsoTimer < 1000) + { + G_Damage(grappler, self, self, NULL, self->client->ps.origin, 30, 0, MOD_MELEE); + //G_Sound( grappler, CHAN_AUTO, G_SoundIndex( va( "sound/weapons/melee/punch%d", Q_irand( 1, 4 ) ) ) ); + + //it might try to put them into a pain anim or something, so override it back again + if (grappler->health > 0) + { + grappler->client->ps.torsoTimer = 1000; + //G_SetAnim(grappler, &grappler->client->pers.cmd, SETANIM_BOTH, BOTH_GETUP3, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + grappler->client->grappleState = 0; + } + else + { //override death anim + grappler->client->ps.torsoAnim = BOTH_DEADFLOP1; + grappler->client->ps.legsAnim = BOTH_DEADFLOP1; + } + + self->client->grappleState = 0; + } + } + } + else + { //? + } + } + } + } + } + + //If this is a listen server (client+server running on same machine), + //then lets try to steal the skeleton/etc data off the client instance + //for this entity to save us processing time. + clientOverride = trap_G2API_OverrideServer(self->ghoul2); + + saberNum = self->client->ps.saberEntityNum; + + if (!saberNum) + { + saberNum = self->client->saberStoredIndex; + } + + if (!saberNum) + { + returnAfterUpdate = 1; + goto nextStep; + } + + mySaber = &g_entities[saberNum]; + + if (self->health < 1) + { //we don't want to waste precious CPU time calculating saber positions for corpses. But we want to avoid the saber ent position lagging on spawn, so.. + //I guess it's good to keep the position updated even when contents are 0 + if (mySaber && ((mySaber->r.contents & CONTENTS_LIGHTSABER) || mySaber->r.contents == 0) && !self->client->ps.saberInFlight) + { //Since we haven't got a bolt position, place it on top of the player origin. + VectorCopy(self->client->ps.origin, mySaber->r.currentOrigin); + } + + //I don't want to return now actually, I want to keep g2 instances for corpses up to + //date because I'm doing better corpse hit detection/dismem (particularly for the + //npc's) + //return; + } + + if ( BG_SuperBreakWinAnim( self->client->ps.torsoAnim ) ) + { + self->client->ps.weaponstate = WEAPON_FIRING; + } + if (self->client->ps.weapon != WP_SABER || + self->client->ps.weaponstate == WEAPON_RAISING || + self->client->ps.weaponstate == WEAPON_DROPPING || + self->health < 1) + { + if (!self->client->ps.saberInFlight) + { + returnAfterUpdate = 1; + } + } + + if (self->client->ps.saberThrowDelay < level.time) + { + if ( !self->client->saber[0].throwable ) + {//cant throw it normally! + if ( self->client->saber[0].singleBladeThrowable ) + {//but can throw it if only have 1 blade on + if ( self->client->saber[0].numBlades > 1 + && self->client->ps.saberHolstered == 1 ) + {//have multiple blades and only one blade on + self->client->ps.saberCanThrow = qtrue;//qfalse; + //huh? want to be able to throw then right? + } + else + {//multiple blades on, can't throw + self->client->ps.saberCanThrow = qfalse; + } + } + else + {//never can throw it + self->client->ps.saberCanThrow = qfalse; + } + } + else + {//can throw it! + self->client->ps.saberCanThrow = qtrue; + } + } +nextStep: + if (self->client->ps.fd.forcePowersActive & (1 << FP_RAGE)) + { + animSpeedScale = 2; + } + + VectorCopy(self->client->ps.origin, properOrigin); + + //try to predict the origin based on velocity so it's more like what the client is seeing + VectorCopy(self->client->ps.velocity, addVel); + VectorNormalize(addVel); + + if (self->client->ps.velocity[0] < 0) + { + fVSpeed += (-self->client->ps.velocity[0]); + } + else + { + fVSpeed += self->client->ps.velocity[0]; + } + if (self->client->ps.velocity[1] < 0) + { + fVSpeed += (-self->client->ps.velocity[1]); + } + else + { + fVSpeed += self->client->ps.velocity[1]; + } + if (self->client->ps.velocity[2] < 0) + { + fVSpeed += (-self->client->ps.velocity[2]); + } + else + { + fVSpeed += self->client->ps.velocity[2]; + } + + //fVSpeed *= 0.08; + fVSpeed *= 1.6f/g_svfps.value; + + //Cap it off at reasonable values so the saber box doesn't go flying ahead of us or + //something if we get a big speed boost from something. + if (fVSpeed > 70) + { + fVSpeed = 70; + } + if (fVSpeed < -70) + { + fVSpeed = -70; + } + + properOrigin[0] += addVel[0]*fVSpeed; + properOrigin[1] += addVel[1]*fVSpeed; + properOrigin[2] += addVel[2]*fVSpeed; + + properAngles[0] = 0; + if (self->s.number < MAX_CLIENTS && self->client->ps.m_iVehicleNum) + { + vehEnt = &g_entities[self->client->ps.m_iVehicleNum]; + if (vehEnt->inuse && vehEnt->client && vehEnt->m_pVehicle) + { + properAngles[1] = vehEnt->m_pVehicle->m_vOrientation[YAW]; + } + else + { + properAngles[1] = self->client->ps.viewangles[YAW]; + vehEnt = NULL; + } + } + else + { + properAngles[1] = self->client->ps.viewangles[YAW]; + } + properAngles[2] = 0; + + AnglesToAxis( properAngles, legAxis ); + + UpdateClientRenderinfo(self, properOrigin, properAngles); + + if (!clientOverride) + { //if we get the client instance we don't need to do this + G_G2PlayerAngles( self, legAxis, properAngles ); + } + + if (vehEnt) + { + properAngles[1] = vehEnt->m_pVehicle->m_vOrientation[YAW]; + } + + if (returnAfterUpdate && saberNum) + { //We don't even need to do GetBoltMatrix if we're only in here to keep the g2 server instance in sync + //but keep our saber entity in sync too, just copy it over our origin. + + //I guess it's good to keep the position updated even when contents are 0 + if (mySaber && ((mySaber->r.contents & CONTENTS_LIGHTSABER) || mySaber->r.contents == 0) && !self->client->ps.saberInFlight) + { //Since we haven't got a bolt position, place it on top of the player origin. + VectorCopy(self->client->ps.origin, mySaber->r.currentOrigin); + } + + goto finalUpdate; + } + + if (returnAfterUpdate) + { + goto finalUpdate; + } + + //We'll get data for blade 0 first no matter what it is and stick them into + //the constant ("_Always") values. Later we will handle going through each blade. + trap_G2API_GetBoltMatrix(self->ghoul2, 1, 0, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, boltAngles); + + //immediately store these values so we don't have to recalculate this again + if (self->client->lastSaberStorageTime && (level.time - self->client->lastSaberStorageTime) < 200) + { //alright + VectorCopy(self->client->lastSaberBase_Always, self->client->olderSaberBase); + self->client->olderIsValid = qtrue; + } + else + { + self->client->olderIsValid = qfalse; + } + + VectorCopy(boltOrigin, self->client->lastSaberBase_Always); + VectorCopy(boltAngles, self->client->lastSaberDir_Always); + self->client->lastSaberStorageTime = level.time; + + VectorCopy(boltAngles, rawAngles); + + VectorMA( boltOrigin, self->client->saber[0].blade[0].lengthMax, boltAngles, end ); + + if (self->client->ps.saberEntityNum) + { + //I guess it's good to keep the position updated even when contents are 0 + if (mySaber && ((mySaber->r.contents & CONTENTS_LIGHTSABER) || mySaber->r.contents == 0) && !self->client->ps.saberInFlight) + { //place it roughly in the middle of the saber.. + VectorMA( boltOrigin, self->client->saber[0].blade[0].lengthMax, boltAngles, mySaber->r.currentOrigin ); + } + } + + boltAngles[YAW] = self->client->ps.viewangles[YAW]; + +/* { + static int lastDTime = 0; + if (lastDTime < level.time) + { + G_TestLine(boltOrigin, end, 0x0000ff, 200); + lastDTime = level.time + 200; + } + } +*/ + if (self->client->ps.saberInFlight) + { //do the thrown-saber stuff + gentity_t *saberent = &g_entities[saberNum]; + + if (saberent) + { + if (!self->client->ps.saberEntityState && self->client->ps.saberEntityNum) + { + vec3_t startorg, startang, dir; + + VectorCopy(boltOrigin, saberent->r.currentOrigin); + + VectorCopy(boltOrigin, startorg); + VectorCopy(boltAngles, startang); + + //startang[0] = 90; + //Instead of this we'll sort of fake it and slowly tilt it down on the client via + //a perframe method (which doesn't actually affect where or how the saber hits) + + saberent->r.svFlags &= ~(SVF_NOCLIENT); + VectorCopy(startorg, saberent->s.pos.trBase); + VectorCopy(startang, saberent->s.apos.trBase); + + VectorCopy(startorg, saberent->s.origin); + VectorCopy(startang, saberent->s.angles); + + saberent->s.saberInFlight = qtrue; + + saberent->s.apos.trType = TR_LINEAR; + saberent->s.apos.trDelta[0] = 0; + saberent->s.apos.trDelta[1] = 800; + saberent->s.apos.trDelta[2] = 0; + + saberent->s.pos.trType = TR_LINEAR; + saberent->s.eType = ET_GENERAL; + saberent->s.eFlags = 0; + + WP_SaberAddG2Model( saberent, self->client->saber[0].model, self->client->saber[0].skin ); + + saberent->s.modelGhoul2 = 127; + + saberent->parent = self; + + self->client->ps.saberEntityState = 1; + + //Projectile stuff: + AngleVectors(self->client->ps.viewangles, dir, NULL, NULL); + + saberent->nextthink = level.time + FRAMETIME; + saberent->think = saberFirstThrown; + + saberent->damage = SABER_THROWN_HIT_DAMAGE; + saberent->methodOfDeath = MOD_SABER; + saberent->splashMethodOfDeath = MOD_SABER; + saberent->s.solid = 2; + saberent->r.contents = CONTENTS_LIGHTSABER; + + saberent->genericValue5 = 0; + + VectorSet( saberent->r.mins, SABERMINS_X, SABERMINS_Y, SABERMINS_Z ); + VectorSet( saberent->r.maxs, SABERMAXS_X, SABERMAXS_Y, SABERMAXS_Z ); + + saberent->s.genericenemyindex = self->s.number+1024; + + saberent->touch = thrownSaberTouch; + + saberent->s.weapon = WP_SABER; + + VectorScale(dir, 400, saberent->s.pos.trDelta ); + saberent->s.pos.trTime = level.time; + + saberent->s.loopSound = saberSpinSound; + saberent->s.loopIsSoundset = qfalse; + + self->client->ps.saberDidThrowTime = level.time; + + self->client->dangerTime = level.time; + self->client->ps.eFlags &= ~EF_INVULNERABLE; + self->client->invulnerableTimer = 0; + + trap_LinkEntity(saberent); + } + else if (self->client->ps.saberEntityNum) //only do this stuff if your saber is active and has not been knocked out of the air. + { + VectorCopy(boltOrigin, saberent->pos1); + trap_LinkEntity(saberent); + + if (saberent->genericValue5 == PROPER_THROWN_VALUE) + { //return to the owner now, this is a bad state to be in for here.. + saberent->genericValue5 = 0; + saberent->think = SaberUpdateSelf; + saberent->nextthink = level.time; + WP_SaberRemoveG2Model( saberent ); + + self->client->ps.saberInFlight = qfalse; + self->client->ps.saberEntityState = 0; + self->client->ps.saberThrowDelay = level.time + 500; + self->client->ps.saberCanThrow = qfalse; + } + } + } + } + + /* + if (self->client->ps.saberInFlight) + { //if saber is thrown then only do the standard stuff for the left hand saber + rSaberNum = 1; + } + */ + + if (!BG_SabersOff(&self->client->ps)) + { + gentity_t *saberent = &g_entities[saberNum]; + + if (!self->client->ps.saberInFlight && saberent) + { + saberent->r.svFlags |= (SVF_NOCLIENT); + saberent->r.contents = CONTENTS_LIGHTSABER; + SetSaberBoxSize(saberent); + saberent->s.loopSound = 0; + saberent->s.loopIsSoundset = qfalse; + } + + if (self->client->ps.saberLockTime > level.time && self->client->ps.saberEntityNum) + { + if (self->client->ps.saberIdleWound < level.time) + { + gentity_t *te; + vec3_t dir; + te = G_TempEntity( g_entities[saberNum].r.currentOrigin, EV_SABER_BLOCK ); + VectorSet( dir, 0, 1, 0 ); + VectorCopy(g_entities[saberNum].r.currentOrigin, te->s.origin); + VectorCopy(dir, te->s.angles); + te->s.eventParm = 1; + + self->client->ps.saberIdleWound = level.time + Q_irand(400, 600); + } + + while (rSaberNum < MAX_SABERS) + { + rBladeNum = 0; + while (rBladeNum < self->client->saber[rSaberNum].numBlades) + { //Don't bother updating the bolt for each blade for this, it's just a very rough fallback method for during saberlocks + VectorCopy(boltOrigin, self->client->saber[saberNum].blade[rBladeNum].trail.base); + VectorCopy(end, self->client->saber[saberNum].blade[rBladeNum].trail.tip); + + rBladeNum++; + } + + rSaberNum++; + } + self->client->hasCurrentPosition = qtrue; + + self->client->ps.saberBlocked = BLOCKED_NONE; + + goto finalUpdate; + } + + //reset it in case we used it for cycling before + rSaberNum = rBladeNum = 0; + + if (self->client->ps.saberInFlight) + { //if saber is thrown then only do the standard stuff for the left hand saber + if (!self->client->ps.saberEntityNum) + { //however, if saber is not in flight but rather knocked away, our left saber is off, and thus we may do nothing. + rSaberNum = 1;//was 2? + } + else + {//thrown saber still in flight, so do damage + rSaberNum = 0;//was 1? + } + } + + WP_SaberClearDamage(); + saberDoClashEffect = qfalse; + + //Now cycle through each saber and each blade on the saber and do damage traces. + while (rSaberNum < MAX_SABERS) + { + if (!self->client->saber[rSaberNum].model[0]) + { + rSaberNum++; + continue; + } + + /* + if (rSaberNum == 0 && (self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM))) + { //don't do saber 0 is the right arm is broken + rSaberNum++; + continue; + } + */ + //for now I'm keeping a broken right arm swingable, it will just look and act damaged + //but still be useable + + if (rSaberNum == 1 && (self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM))) + { //don't to saber 1 if the left arm is broken + break; + } + if (rSaberNum > 0 + && self->client->saber[1].model + && self->client->saber[1].model[0] + && self->client->ps.saberHolstered == 1 ) + { //don't to saber 2 if it's off + break; + } + rBladeNum = 0; + while (rBladeNum < self->client->saber[rSaberNum].numBlades) + { + //update muzzle data for the blade + VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePointOld); + VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDirOld); + + if ( rBladeNum > 0 //more than one blade + && (!self->client->saber[1].model||!self->client->saber[1].model[0])//not using dual blades + && self->client->saber[rSaberNum].numBlades > 1//using a multi-bladed saber + && self->client->ps.saberHolstered == 1 )// + { //don't to extra blades if they're off + break; + } + //get the new data + //then update the bolt pos/dir. rBladeNum corresponds to the bolt index because blade bolts are added in order. + if ( rSaberNum == 0 && self->client->ps.saberInFlight ) + { + if ( !self->client->ps.saberEntityNum ) + {//dropped it... shouldn't get here, but... + //assert(0); + //FIXME: It's getting here a lot actually.... + rSaberNum++; + rBladeNum = 0; + continue; + } + else + { + gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum]; + vec3_t saberOrg, saberAngles; + if ( !saberEnt + || !saberEnt->inuse + || !saberEnt->ghoul2 ) + {//wtf? + rSaberNum++; + rBladeNum = 0; + continue; + } + if ( saberent->s.saberInFlight ) + {//spinning + BG_EvaluateTrajectory( &saberEnt->s.pos, level.time+50, saberOrg ); + BG_EvaluateTrajectory( &saberEnt->s.apos, level.time+50, saberAngles ); + } + else + {//coming right back + vec3_t saberDir; + BG_EvaluateTrajectory( &saberEnt->s.pos, level.time, saberOrg ); + VectorSubtract( self->r.currentOrigin, saberOrg, saberDir ); + vectoangles( saberDir, saberAngles ); + } + trap_G2API_GetBoltMatrix(saberEnt->ghoul2, 0, rBladeNum, &boltMatrix, saberAngles, saberOrg, level.time, NULL, self->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir); + VectorCopy( self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, boltOrigin ); + VectorMA( boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].lengthMax, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, end ); + } + + } + else + { + trap_G2API_GetBoltMatrix(self->ghoul2, rSaberNum+1, rBladeNum, &boltMatrix, properAngles, properOrigin, level.time, NULL, self->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir); + VectorCopy( self->client->saber[rSaberNum].blade[rBladeNum].muzzlePoint, boltOrigin ); + VectorMA( boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].lengthMax, self->client->saber[rSaberNum].blade[rBladeNum].muzzleDir, end ); + } + + self->client->saber[rSaberNum].blade[rBladeNum].storageTime = level.time; + + if (self->client->hasCurrentPosition && d_saberInterpolate.integer) + { + if (self->client->ps.weaponTime <= 0) + { //rww - 07/17/02 - don't bother doing the extra stuff unless actually attacking. This is in attempt to save CPU. + CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse); + } + else if (d_saberInterpolate.integer == 1) + { + int trMask = CONTENTS_LIGHTSABER|CONTENTS_BODY; + int sN = 0; + qboolean gotHit = qfalse; + qboolean clientUnlinked[MAX_CLIENTS]; + qboolean skipSaberTrace = qfalse; + + if (!g_saberTraceSaberFirst.integer) + { + skipSaberTrace = qtrue; + } + else if (g_saberTraceSaberFirst.integer >= 2 && + g_gametype.integer != GT_DUEL && + g_gametype.integer != GT_POWERDUEL && + !self->client->ps.duelInProgress) + { //if value is >= 2, and not in a duel, skip + skipSaberTrace = qtrue; + } + + if (skipSaberTrace) + { //skip the saber-contents-only trace and get right to the full trace + trMask = (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT); + } + else + { + while (sN < MAX_CLIENTS) + { + if (g_entities[sN].inuse && g_entities[sN].client && g_entities[sN].r.linked && g_entities[sN].health > 0 && (g_entities[sN].r.contents & CONTENTS_BODY)) + { //Take this mask off before the saber trace, because we want to hit the saber first + g_entities[sN].r.contents &= ~CONTENTS_BODY; + clientUnlinked[sN] = qtrue; + } + else + { + clientUnlinked[sN] = qfalse; + } + sN++; + } + } + + while (!gotHit) + { + if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, trMask, qfalse)) + { + if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qtrue, trMask, qfalse)) + { + vec3_t oldSaberStart; + vec3_t oldSaberEnd; + vec3_t saberAngleNow; + vec3_t saberAngleBefore; + vec3_t saberMidDir; + vec3_t saberMidAngle; + vec3_t saberMidPoint; + vec3_t saberMidEnd; + vec3_t saberSubBase; + float deltaX, deltaY, deltaZ; + + VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.base, oldSaberStart); + VectorCopy(self->client->saber[rSaberNum].blade[rBladeNum].trail.tip, oldSaberEnd); + + VectorSubtract(oldSaberEnd, oldSaberStart, saberAngleBefore); + vectoangles(saberAngleBefore, saberAngleBefore); + + VectorSubtract(end, boltOrigin, saberAngleNow); + vectoangles(saberAngleNow, saberAngleNow); + + deltaX = AngleDelta(saberAngleBefore[0], saberAngleNow[0]); + deltaY = AngleDelta(saberAngleBefore[1], saberAngleNow[1]); + deltaZ = AngleDelta(saberAngleBefore[2], saberAngleNow[2]); + + if ( (deltaX != 0 || deltaY != 0 || deltaZ != 0) && deltaX < 180 && deltaY < 180 && deltaZ < 180 && (BG_SaberInAttack(self->client->ps.saberMove) || PM_SaberInTransition(self->client->ps.saberMove)) ) + { //don't go beyond here if we aren't attacking/transitioning or the angle is too large. + //and don't bother if the angle is the same + saberMidAngle[0] = saberAngleBefore[0] + (deltaX/2); + saberMidAngle[1] = saberAngleBefore[1] + (deltaY/2); + saberMidAngle[2] = saberAngleBefore[2] + (deltaZ/2); + + //Now that I have the angle, I'll just say the base for it is the difference between the two start + //points (even though that's quite possibly completely false) + VectorSubtract(boltOrigin, oldSaberStart, saberSubBase); + saberMidPoint[0] = boltOrigin[0] + (saberSubBase[0]*0.5); + saberMidPoint[1] = boltOrigin[1] + (saberSubBase[1]*0.5); + saberMidPoint[2] = boltOrigin[2] + (saberSubBase[2]*0.5); + + AngleVectors(saberMidAngle, saberMidDir, 0, 0); + saberMidEnd[0] = saberMidPoint[0] + saberMidDir[0]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax; + saberMidEnd[1] = saberMidPoint[1] + saberMidDir[1]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax; + saberMidEnd[2] = saberMidPoint[2] + saberMidDir[2]*self->client->saber[rSaberNum].blade[rBladeNum].lengthMax; + + //I'll just trace straight out and not even trace between positions to save speed. + if (CheckSaberDamage(self, rSaberNum, rBladeNum, saberMidPoint, saberMidEnd, qfalse, trMask, qfalse)) + { + gotHit = qtrue; + } + } + } + else + { + gotHit = qtrue; + } + } + else + { + gotHit = qtrue; + } + + if (g_saberTraceSaberFirst.integer) + { + sN = 0; + while (sN < MAX_CLIENTS) + { + if (clientUnlinked[sN]) + { //Make clients clip properly again. + if (g_entities[sN].inuse && g_entities[sN].health > 0) + { + g_entities[sN].r.contents |= CONTENTS_BODY; + } + } + sN++; + } + } + + if (!gotHit) + { + if (trMask != (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT)) + { + trMask = (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT); + } + else + { + gotHit = qtrue; //break out of the loop + } + } + } + } + else if (d_saberInterpolate.integer) //anything but 0 or 1, use the old plain method. + { + if (!CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse)) + { + CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qtrue, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse); + } + } + } + else if ( d_saberSPStyleDamage.integer ) + { + G_SPSaberDamageTraceLerped( self, rSaberNum, rBladeNum, boltOrigin, end, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT) ); + } + else + { + CheckSaberDamage(self, rSaberNum, rBladeNum, boltOrigin, end, qfalse, (MASK_PLAYERSOLID|CONTENTS_LIGHTSABER|MASK_SHOT), qfalse); + } + + VectorCopy(boltOrigin, self->client->saber[rSaberNum].blade[rBladeNum].trail.base); + VectorCopy(end, self->client->saber[rSaberNum].blade[rBladeNum].trail.tip); + //VectorCopy(boltOrigin, self->client->lastSaberBase); + //VectorCopy(end, self->client->lastSaberTip); + self->client->hasCurrentPosition = qtrue; + + rBladeNum++; + } + + rSaberNum++; + } + + //now actually go through and apply all the damage we did + WP_SaberApplyDamage( self ); + WP_SaberDoClash(); + + if (mySaber && mySaber->inuse) + { + trap_LinkEntity(mySaber); + } + + if (!self->client->ps.saberInFlight) + { + self->client->ps.saberEntityState = 0; + } + } +finalUpdate: + if (clientOverride) + { //if we get the client instance we don't even need to bother setting anims and stuff + return; + } + + G_UpdateClientAnims(self, animSpeedScale); +} + +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; + vec3_t clEye; + float rightdot; + float zdiff; + + VectorCopy(self->client->ps.origin, clEye); + clEye[2] += self->client->ps.viewheight; + + VectorSubtract( hitloc, clEye, 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] - clEye[2]; + + if ( zdiff > 0 ) + { + 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 > -20 )//20 ) + { + if ( zdiff < -10 )//30 ) + {//hmm, pretty low, but not low enough to use the low block, so we need to duck + + } + if ( rightdot > 0.1 ) + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + } + else if ( rightdot < -0.1 ) + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + } + else + { + 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; + } + } + + if ( missileBlock ) + { + self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked ); + } +} + +void WP_SaberBlock( gentity_t *playerent, vec3_t hitloc, qboolean missileBlock ) +{ + vec3_t diff, fwdangles={0,0,0}, right; + float rightdot; + float zdiff; + + VectorSubtract(hitloc, playerent->client->ps.origin, 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) + RandFloat(-0.2f,0.2f); + zdiff = hitloc[2] - playerent->client->ps.origin[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 ); + } +} + +int WP_SaberCanBlock(gentity_t *self, vec3_t point, int dflags, int mod, qboolean projectile, int attackStr) +{ + qboolean thrownSaber = qfalse; + float blockFactor = 0; + + if (!self || !self->client || !point) + { + return 0; + } + + if (attackStr == 999) + { + attackStr = 0; + thrownSaber = qtrue; + } + + if (BG_SaberInAttack(self->client->ps.saberMove)) + { + return 0; + } + + if (PM_InSaberAnim(self->client->ps.torsoAnim) && !self->client->ps.saberBlocked && + self->client->ps.saberMove != LS_READY && self->client->ps.saberMove != LS_NONE) + { + if ( self->client->ps.saberMove < LS_PARRY_UP || self->client->ps.saberMove > LS_REFLECT_LL ) + { + return 0; + } + } + + if (PM_SaberInBrokenParry(self->client->ps.saberMove)) + { + return 0; + } + + if (!self->client->ps.saberEntityNum) + { //saber is knocked away + return 0; + } + + if (BG_SabersOff( &self->client->ps )) + { + return 0; + } + + if (self->client->ps.weapon != WP_SABER) + { + return 0; + } + + if (self->client->ps.weaponstate == WEAPON_RAISING) + { + return 0; + } + + if (self->client->ps.saberInFlight) + { + return 0; + } + + if ((self->client->pers.cmd.buttons & BUTTON_ATTACK)/* && + (projectile || attackStr == FORCE_LEVEL_3)*/) + { //don't block when the player is trying to slash, if it's a projectile or he's doing a very strong attack + return 0; + } + + //Removed this for now, the new broken parry stuff should handle it. This is how + //blocks were decided before the 1.03 patch (as you can see, it was STUPID.. for the most part) + /* + if (attackStr == FORCE_LEVEL_3) + { + if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3) + { + if (Q_irand(1, 10) < 3) + { + return 0; + } + } + else + { + return 0; + } + } + + if (attackStr == FORCE_LEVEL_2 && Q_irand(1, 10) < 3) + { + if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_3) + { + //do nothing for now + } + else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] >= FORCE_LEVEL_2) + { + if (Q_irand(1, 10) < 5) + { + return 0; + } + } + else + { + return 0; + } + } + + if (attackStr == FORCE_LEVEL_1 && !self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] && + Q_irand(1, 40) < 3) + { //if I have no defense level at all then I might be unable to block a level 1 attack (but very rarely) + return 0; + } + */ + + if (SaberAttacking(self)) + { //attacking, can't block now + return 0; + } + + if (self->client->ps.saberMove != LS_READY && + !self->client->ps.saberBlocking) + { + return 0; + } + + if (self->client->ps.saberBlockTime >= level.time) + { + return 0; + } + + if (self->client->ps.forceHandExtend != HANDEXTEND_NONE) + { + return 0; + } + + if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == FORCE_LEVEL_3) + { + if (d_saberGhoul2Collision.integer) + { + blockFactor = 0.3f; + } + else + { + blockFactor = 0.05f; + } + } + else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == FORCE_LEVEL_2) + { + blockFactor = 0.6f; + } + else if (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE] == FORCE_LEVEL_1) + { + blockFactor = 0.9f; + } + else + { //for now we just don't get to autoblock with no def + return 0; + } + + if (thrownSaber) + { + blockFactor -= 0.25f; + } + + if (attackStr) + { //blocking a saber, not a projectile. + blockFactor -= 0.25f; + } + + if (!InFront( point, self->client->ps.origin, self->client->ps.viewangles, blockFactor )) //orig 0.2f + { + return 0; + } + + if (projectile) + { + WP_SaberBlockNonRandom(self, point, projectile); + } + return 1; +} + +qboolean HasSetSaberOnly(void) +{ + int i = 0; + int wDisable = 0; + + if (g_gametype.integer == GT_JEDIMASTER) + { //set to 0 + return qfalse; + } + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { + wDisable = g_duelWeaponDisable.integer; + } + else + { + wDisable = g_weaponDisable.integer; + } + + while (i < WP_NUM_WEAPONS) + { + if (!(wDisable & (1 << i)) && + i != WP_SABER && i != WP_NONE) + { + return qfalse; + } + + i++; + } + + return qtrue; +} + diff --git a/codemp/game/w_saber.h b/codemp/game/w_saber.h new file mode 100644 index 0000000..336e87f --- /dev/null +++ b/codemp/game/w_saber.h @@ -0,0 +1,74 @@ +#define ARMOR_EFFECT_TIME 500 + +//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 1//2 +#define SES_RETURNING 1//3 +//This is a hack because ATM the saberEntityState is only non-0 if out or 0 if in, and we +//at least want NPCs knowing when their saber is out regardless. + + +#define JSF_AMBUSH 16 //ambusher Jedi + +#define SABER_RADIUS_STANDARD 3.0f +#define SABER_REFLECT_MISSILE_CONE 0.2f + +#define FORCE_POWER_MAX 100 +#define MAX_GRIP_DISTANCE 256 +#define MAX_TRICK_DISTANCE 512 +#define FORCE_JUMP_CHARGE_TIME 6400//3000.0f +#define GRIP_DRAIN_AMOUNT 30 +#define FORCE_LIGHTNING_RADIUS 300 +#define MAX_DRAIN_DISTANCE 512 + +typedef enum +{ + FJ_FORWARD, + FJ_BACKWARD, + FJ_RIGHT, + FJ_LEFT, + FJ_UP +}; + +typedef enum +{ + EVASION_NONE = 0, + EVASION_PARRY, + EVASION_DUCK_PARRY, + EVASION_JUMP_PARRY, + EVASION_DODGE, + EVASION_JUMP, + EVASION_DUCK, + EVASION_FJUMP, + EVASION_CARTWHEEL, + EVASION_OTHER, + NUM_EVASION_TYPES +} evasionType_t; + +extern vmCvar_t g_MaxHolocronCarry; + +#define SABERMINS_X -3.0f//-24.0f +#define SABERMINS_Y -3.0f//-24.0f +#define SABERMINS_Z -3.0f//-8.0f +#define SABERMAXS_X 3.0f//24.0f +#define SABERMAXS_Y 3.0f//24.0f +#define SABERMAXS_Z 3.0f//8.0f +#define SABER_MIN_THROW_DIST 80.0f + +#include "../namespace_begin.h" +extern int forcePowerNeeded[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS]; +extern float forceJumpHeight[NUM_FORCE_POWER_LEVELS]; +extern float forceJumpStrength[NUM_FORCE_POWER_LEVELS]; +extern float forcePushPullRadius[NUM_FORCE_POWER_LEVELS]; +#include "../namespace_end.h" diff --git a/codemp/ghoul2/G2.h b/codemp/ghoul2/G2.h new file mode 100644 index 0000000..69bf6d2 --- /dev/null +++ b/codemp/ghoul2/G2.h @@ -0,0 +1,40 @@ +#if defined (_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif +#if !defined(G2_H_INC) +#define G2_H_INC + + +#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 +//rww - RAGDOLL_END +#define BONE_ANGLES_IK 0x4000 // the rag flags give more details + +#define BONE_ANGLES_TOTAL ( BONE_ANGLES_PREMULT | BONE_ANGLES_POSTMULT | BONE_ANGLES_REPLACE ) +#define BONE_ANIM_OVERRIDE 0x0008 +#define BONE_ANIM_OVERRIDE_LOOP 0x0010 +#define BONE_ANIM_OVERRIDE_FREEZE ( 0x0040 + BONE_ANIM_OVERRIDE ) +#define BONE_ANIM_BLEND 0x0080 +#define BONE_ANIM_TOTAL ( BONE_ANIM_OVERRIDE | BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE_FREEZE | BONE_ANIM_BLEND) + + +// defines to setup the +#define ENTITY_WIDTH 12 +#define MODEL_WIDTH 10 +#define BOLT_WIDTH 10 + +#define MODEL_AND ((1< + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#include +#pragma warning (pop) + +#ifdef _FULL_G2_LEAK_CHECKING +int g_Ghoul2Allocations = 0; +int g_G2ServerAlloc = 0; +int g_G2ClientAlloc = 0; +int g_G2AllocServer = 0; + +//stupid debug crap to track leaks in case they happen. +//we used to shove everything into a map and delete it all and not care about +//leaks, but that was not the Right Thing. -rww +#define MAX_TRACKED_ALLOC 4096 +static bool g_G2AllocTrackInit = false; //want to keep this thing contained +static CGhoul2Info_v *g_G2AllocTrack[MAX_TRACKED_ALLOC]; + +void G2_DEBUG_InitPtrTracker(void) +{ + memset(g_G2AllocTrack, 0, sizeof(g_G2AllocTrack)); + g_G2AllocTrackInit = true; +} + +void G2_DEBUG_ReportLeaks(void) +{ + int i = 0; + + if (!g_G2AllocTrackInit) + { + Com_Printf("g2 leak tracker was never initialized!\n"); + return; + } + + while (i < MAX_TRACKED_ALLOC) + { + if (g_G2AllocTrack[i]) + { + Com_Printf("Bad guy found in slot %i, attempting to access...", i); + CGhoul2Info_v &g2v = *g_G2AllocTrack[i]; + CGhoul2Info &g2 = g2v[0]; + + if (g2v.IsValid() && g2.mFileName && g2.mFileName[0]) + { + Com_Printf("Bad guy's filename is %s\n", g2.mFileName); + } + else + { + Com_Printf("He's not valid! BURN HIM!\n"); + } + } + i++; + } +} + +void G2_DEBUG_ShovePtrInTracker(CGhoul2Info_v *g2) +{ + int i = 0; + + if (!g_G2AllocTrackInit) + { + G2_DEBUG_InitPtrTracker(); + } + + if (!g_G2AllocTrackInit) + { + G2_DEBUG_InitPtrTracker(); + } + + while (i < MAX_TRACKED_ALLOC) + { + if (!g_G2AllocTrack[i]) + { + g_G2AllocTrack[i] = g2; + return; + } + i++; + } + + CGhoul2Info_v &g2v = *g2; + + if (g2v[0].currentModel && g2v[0].currentModel->name && g2v[0].currentModel->name[0]) + { + Com_Printf("%s could not be fit into g2 debug instance tracker.\n", g2v[0].currentModel->name); + } + else + { + Com_Printf("Crap g2 instance passed to instance tracker (in).\n"); + } +} + +void G2_DEBUG_RemovePtrFromTracker(CGhoul2Info_v *g2) +{ + int i = 0; + + if (!g_G2AllocTrackInit) + { + G2_DEBUG_InitPtrTracker(); + } + + while (i < MAX_TRACKED_ALLOC) + { + if (g_G2AllocTrack[i] == g2) + { + g_G2AllocTrack[i] = NULL; + return; + } + i++; + } + + CGhoul2Info_v &g2v = *g2; + + if (g2v[0].currentModel && g2v[0].currentModel->name && g2v[0].currentModel->name[0]) + { + Com_Printf("%s not in g2 debug instance tracker.\n", g2v[0].currentModel->name); + } + else + { + Com_Printf("Crap g2 instance passed to instance tracker (out).\n"); + } +} +#endif + +extern mdxaBone_t worldMatrix; +extern mdxaBone_t worldMatrixInv; + +qboolean G2_SetupModelPointers(CGhoul2Info *ghlInfo); +qboolean G2_SetupModelPointers(CGhoul2Info_v &ghoul2); +qboolean G2_TestModelPointers(CGhoul2Info *ghlInfo); + +//rww - RAGDOLL_BEGIN +#define NUM_G2T_TIME (2) +static int G2TimeBases[NUM_G2T_TIME]; + +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; +} +//rww - RAGDOLL_END + +//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. +#ifdef _G2_LISTEN_SERVER_OPT +CGhoul2Info_v *g2ClientAttachments[MAX_GENTITIES]; +#endif + +void G2API_AttachInstanceToEntNum(CGhoul2Info_v &ghoul2, int entityNum, qboolean server) +{ //Assign the pointers in the arrays +#ifdef _G2_LISTEN_SERVER_OPT + if (server) + { + ghoul2[0].entityNum = entityNum; + } + else + { + g2ClientAttachments[entityNum] = &ghoul2; + } +#endif +} + +void G2API_ClearAttachedInstance(int entityNum) +{ +#ifdef _G2_LISTEN_SERVER_OPT + g2ClientAttachments[entityNum] = NULL; +#endif +} + +void G2API_CleanEntAttachments(void) +{ +#ifdef _G2_LISTEN_SERVER_OPT + int i = 0; + + while (i < MAX_GENTITIES) + { + g2ClientAttachments[i] = NULL; + i++; + } +#endif +} + +void RemoveBoneCache(CBoneCache *boneCache); +#ifdef _G2_LISTEN_SERVER_OPT +void CopyBoneCache(CBoneCache *to, CBoneCache *from); +#endif + +qboolean G2API_OverrideServerWithClientData(CGhoul2Info *serverInstance) +{ +#ifndef _G2_LISTEN_SERVER_OPT + return qfalse; +#else + CGhoul2Info *clientInstance; + + if (com_dedicated->integer) + { //No client to get from! + return qfalse; + } + + if (!g2ClientAttachments[serverInstance->entityNum]) + { //No clientside instance is attached to this entity + return qfalse; + } + + CGhoul2Info_v &g2Ref = *g2ClientAttachments[serverInstance->entityNum]; + clientInstance = &g2Ref[0]; + + int frameNum = G2API_GetTime(0); + + if (clientInstance->mSkelFrameNum != frameNum) + { //it has to be constructed already + return qfalse; + } + + if (!clientInstance->mBoneCache) + { //that just won't do + return qfalse; + } + + //Just copy over the essentials + serverInstance->aHeader = clientInstance->aHeader; + serverInstance->animModel = clientInstance->animModel; + serverInstance->currentAnimModelSize = clientInstance->currentAnimModelSize; + serverInstance->currentModel = clientInstance->currentModel; + serverInstance->currentModelSize = clientInstance->currentModelSize; + serverInstance->mAnimFrameDefault = clientInstance->mAnimFrameDefault; + serverInstance->mModel = clientInstance->mModel; + serverInstance->mModelindex = clientInstance->mModelindex; + serverInstance->mSurfaceRoot = clientInstance->mSurfaceRoot; + serverInstance->mTransformedVertsArray = clientInstance->mTransformedVertsArray; + + if (!serverInstance->mBoneCache) + { //if this is the case.. I guess we can use the client one instead + serverInstance->mBoneCache = clientInstance->mBoneCache; + } + + //Copy the contents of the client cache over the contents of the server cache + if (serverInstance->mBoneCache != clientInstance->mBoneCache) + { + CopyBoneCache(serverInstance->mBoneCache, clientInstance->mBoneCache); + } + + serverInstance->mSkelFrameNum = clientInstance->mSkelFrameNum; + return qtrue; +#endif +} + +// must be a power of two +#ifdef _XBOX +#define MAX_G2_MODELS (512) +#define G2_MODEL_BITS (9) +#else +#define MAX_G2_MODELS (1024) +#define G2_MODEL_BITS (10) +#endif + +#define G2_INDEX_MASK (MAX_G2_MODELS-1) + +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++) + { + if (mInfos[idx][model].mBoneCache) + { + 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()) + { + 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); + } + } + } + } + else + { + OutputDebugString("No ghoul2 info slots leaked\n"); + } + } +#endif + int New() + { + if (mFreeIndecies.empty()) + { + assert(0); + Com_Error(ERR_FATAL, "Out of ghoul2 info slots"); + + } + // gonna pull from the front, doing a + int idx=*mFreeIndecies.begin(); + mFreeIndecies.erase(mFreeIndecies.begin()); + return mIds[idx]; + } + bool IsValid(int handle) const + { + if (!handle) + { + return false; + } + assert(handle>0); //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; ientities[i].e.ghoul2 == *ghoul2Ptr) + { + char fName[MAX_QPATH]; + char mName[MAX_QPATH]; + + if (ghoul2[0].currentModel) + { + strcpy(mName, ghoul2[0].currentModel->name); + } + else + { + strcpy(mName, "NULL!"); + } + + if (ghoul2[0].mFileName && ghoul2[0].mFileName[0]) + { + strcpy(fName, ghoul2[0].mFileName); + } + else + { + strcpy(fName, "None?!"); + } + + Com_Printf("ERROR, GHOUL2 INSTANCE BEING REMOVED BELONGS TO A REFENTITY!\nThis is in caps because it's important. Tell Rich and save the following text.\n\n"); + Com_Printf("Ref num: %i\nModel: %s\nFilename: %s\n", i, mName, fName); + + R_SetRNumEntities(0); //avoid recursive error + Com_Error(ERR_DROP, "Write down or save this error message, show it to Rich\nRef num: %i\nModel: %s\nFilename: %s\n", i, mName, fName); + } + i++; + } +#endif + +#ifdef _G2_GORE + G2API_ClearSkinGore ( ghoul2 ); +#endif + + ghoul2.~CGhoul2Info_v(); + +#ifdef _FULL_G2_LEAK_CHECKING + if (g_G2AllocServer) + { + g_G2ServerAlloc -= sizeof(*ghoul2Ptr); + } + else + { + g_G2ClientAlloc -= sizeof(*ghoul2Ptr); + } + g_Ghoul2Allocations -= sizeof(*ghoul2Ptr); + G2_DEBUG_RemovePtrFromTracker(*ghoul2Ptr); +#endif + + delete *ghoul2Ptr; + *ghoul2Ptr = NULL; + } +} + +extern qboolean Com_TheHunkMarkHasBeenMade(void); +extern qboolean ShaderHashTableExists(void); +qboolean G2_ShouldRegisterServer(void) +{ + if (currentVM && currentVM == gvm) + { + if (com_cl_running && com_cl_running->integer && + Com_TheHunkMarkHasBeenMade() && ShaderHashTableExists()) + { //if the hunk has been marked then we are now loading client assets so don't load on server. + return qfalse; + } + + return qtrue; + } + return qfalse; +} + +qhandle_t G2API_PrecacheGhoul2Model(const char *fileName) +{ + if (G2_ShouldRegisterServer()) + { + return RE_RegisterServerModel((char *)fileName); + } + else + { + return RE_RegisterModel((char *)fileName); + } +} + +void CL_InitRef( void ); +void R_Register( void ); + +// initialise all that needs to be on a new Ghoul II model +int G2API_InitGhoul2Model(CGhoul2Info_v **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias) +{ + int model; + CGhoul2Info newModel; + + // are we actually asking for a model to be loaded. + if (!fileName || !fileName[0]) + { + assert(0); + return -1; + } + + if (!(*ghoul2Ptr)) + { + *ghoul2Ptr = new CGhoul2Info_v; +#ifdef _FULL_G2_LEAK_CHECKING + if (g_G2AllocServer) + { + g_G2ServerAlloc += sizeof(CGhoul2Info_v); + } + else + { + g_G2ClientAlloc += sizeof(CGhoul2Info_v); + } + g_Ghoul2Allocations += sizeof(CGhoul2Info_v); + G2_DEBUG_ShovePtrInTracker(*ghoul2Ptr); +#endif + } + + CGhoul2Info_v &ghoul2 = *(*ghoul2Ptr); + + // 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()) + { //init should not be used to create additional models, only the first one + assert(ghoul2.size() < 4); //use G2API_CopySpecificG2Model to add models + ghoul2.push_back(CGhoul2Info()); + } + + 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) +{ + if (ghlInfo) + { + ghlInfo->mLodBias = lodBias; + return qtrue; + } + return qfalse; +} + +void G2_SetSurfaceOnOffFromSkin (CGhoul2Info *ghlInfo, qhandle_t renderSkin); +qboolean G2API_SetSkin(CGhoul2Info *ghlInfo, qhandle_t customSkin, qhandle_t renderSkin) +{ + if (ghlInfo) + { + ghlInfo->mCustomSkin = customSkin; + if (renderSkin) + {//this is going to set the surfs on/off matching the skin file + G2_SetSurfaceOnOffFromSkin( ghlInfo, renderSkin ); + } + + return qtrue; + } + return qfalse; +} + +qboolean G2API_SetShader(CGhoul2Info *ghlInfo, qhandle_t customShader) +{ + if (ghlInfo) + { + ghlInfo->mCustomShader = customShader; + return qtrue; + } + return qfalse; +} + +qboolean G2API_SetSurfaceOnOff(CGhoul2Info_v &ghoul2, const char *surfaceName, const int flags) +{ + CGhoul2Info *ghlInfo = NULL; + + if ((int)&ghoul2 && ghoul2.size()>0) + { + ghlInfo = &ghoul2[0]; + } + + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mMeshFrameNum = 0; + return G2_SetSurfaceOnOff(ghlInfo, ghlInfo->mSlist, surfaceName, flags); + } + return qfalse; +} + +int G2API_GetSurfaceOnOff(CGhoul2Info *ghlInfo, const char *surfaceName) +{ + if (ghlInfo) + { + return G2_IsSurfaceOff(ghlInfo, ghlInfo->mSlist, surfaceName); + } + return -1; +} + +qboolean G2API_SetRootSurface(CGhoul2Info_v &ghoul2, const int modelIndex, const char *surfaceName) +{ + if (G2_SetupModelPointers(ghoul2)) + { + return G2_SetRootSurface(ghoul2, modelIndex, surfaceName); + } + + return qfalse; +} + +int G2API_AddSurface(CGhoul2Info *ghlInfo, int surfaceNumber, int polyNumber, float BarycentricI, float BarycentricJ, int lod ) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mMeshFrameNum = 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) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_IsSurfaceRendered(ghlInfo, surfaceName, ghlInfo->mSlist); + } + return -1; +} + +qboolean G2API_HasGhoul2ModelOnIndex(CGhoul2Info_v **ghlRemove, const int modelIndex) +{ + CGhoul2Info_v &ghlInfo = **ghlRemove; + + if (!ghlInfo.size() || (ghlInfo.size() <= modelIndex) || (ghlInfo[modelIndex].mModelindex == -1)) + { + return qfalse; + } + + return qtrue; +} + +qboolean G2API_RemoveGhoul2Model(CGhoul2Info_v **ghlRemove, const int modelIndex) +{ + CGhoul2Info_v &ghlInfo = **ghlRemove; + + // sanity check + if (!ghlInfo.size() || (ghlInfo.size() <= modelIndex) || (ghlInfo[modelIndex].mModelindex == -1)) + { + // 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. + assert(0); + return qfalse; + } + + if (ghlInfo.size() > modelIndex) + { +#ifdef _G2_GORE + // Cleanup the gore attached to this model + if ( ghlInfo[modelIndex].mGoreSetTag ) + { + DeleteGoreSet ( ghlInfo[modelIndex].mGoreSetTag ); + ghlInfo[modelIndex].mGoreSetTag = 0; + } +#endif + + if (ghlInfo[modelIndex].mBoneCache) + { + RemoveBoneCache(ghlInfo[modelIndex].mBoneCache); + ghlInfo[modelIndex].mBoneCache=0; + } + + // clear out the vectors this model used. + ghlInfo[modelIndex].mBlist.clear(); + ghlInfo[modelIndex].mBltlist.clear(); + ghlInfo[modelIndex].mSlist.clear(); + + // set us to be the 'not active' state + ghlInfo[modelIndex].mModelindex = -1; + + int newSize = ghlInfo.size(); + // now look through the list from the back and see if there is a block of -1's we can resize off the end of the list + for (int i=ghlInfo.size()-1; i>-1; i--) + { + if (ghlInfo[i].mModelindex == -1) + { + newSize = i; + } + // once we hit one that isn't a -1, we are done. + else + { + break; + } + } + // do we need to resize? + if (newSize != ghlInfo.size()) + { + // yes, so lets do it + ghlInfo.resize(newSize); + } + + // if we are not using any space, just delete the ghoul2 vector entirely + if (!ghlInfo.size()) + { +#ifdef _FULL_G2_LEAK_CHECKING + if (g_G2AllocServer) + { + g_G2ServerAlloc -= sizeof(*ghlRemove); + } + else + { + g_G2ClientAlloc -= sizeof(*ghlRemove); + } + g_Ghoul2Allocations -= sizeof(*ghlRemove); +#endif + delete *ghlRemove; + *ghlRemove = NULL; + } + } + + + return qtrue; +} + +qboolean G2API_RemoveGhoul2Models(CGhoul2Info_v **ghlRemove) +{//remove 'em ALL! + CGhoul2Info_v &ghlInfo = **ghlRemove; + int modelIndex = 0; + int newSize = 0; + int i; + + // sanity check + if ( !ghlInfo.size() ) + {// if we hit this then we are trying to delete a ghoul2 model on a ghoul2 instance that + // one way or another is already gone. + return qfalse; + } + + for ( modelIndex = 0; modelIndex < ghlInfo.size(); modelIndex++ ) + { + if ( ghlInfo[modelIndex].mModelindex == -1 ) + { + continue; + } +#ifdef _G2_GORE + // Cleanup the gore attached to this model + if ( ghlInfo[modelIndex].mGoreSetTag ) + { + DeleteGoreSet ( ghlInfo[modelIndex].mGoreSetTag ); + ghlInfo[modelIndex].mGoreSetTag = 0; + } +#endif + + if (ghlInfo[modelIndex].mBoneCache) + { + RemoveBoneCache(ghlInfo[modelIndex].mBoneCache); + ghlInfo[modelIndex].mBoneCache=0; + } + + // clear out the vectors this model used. + ghlInfo[modelIndex].mBlist.clear(); + ghlInfo[modelIndex].mBltlist.clear(); + ghlInfo[modelIndex].mSlist.clear(); + + // set us to be the 'not active' state + ghlInfo[modelIndex].mModelindex = -1; + } + + newSize = ghlInfo.size(); + // now look through the list from the back and see if there is a block of -1's we can resize off the end of the list + for (i=ghlInfo.size()-1; i>-1; i--) + { + if (ghlInfo[i].mModelindex == -1) + { + newSize = i; + } + // once we hit one that isn't a -1, we are done. + else + { + break; + } + } + // do we need to resize? + if (newSize != ghlInfo.size()) + { + // yes, so lets do it + ghlInfo.resize(newSize); + } + + // if we are not using any space, just delete the ghoul2 vector entirely + if (!ghlInfo.size()) + { +#ifdef _FULL_G2_LEAK_CHECKING + if (g_G2AllocServer) + { + g_G2ServerAlloc -= sizeof(*ghlRemove); + } + else + { + g_G2ClientAlloc -= sizeof(*ghlRemove); + } + g_Ghoul2Allocations -= sizeof(*ghlRemove); +#endif + delete *ghlRemove; + *ghlRemove = NULL; + } + return qtrue; +} + +//check if a bone exists on skeleton without actually adding to the bone list -rww +qboolean G2API_DoesBoneExist(CGhoul2Info *ghlInfo, const char *boneName) +{ + if (G2_SetupModelPointers(ghlInfo)) + { //model is valid + mdxaHeader_t *mdxa = ghlInfo->currentModel->mdxa; + if (mdxa) + { //get the skeleton data and iterate through the bones + int i; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + + offsets = (mdxaSkelOffsets_t *)((byte *)mdxa + sizeof(mdxaHeader_t)); + + for (i = 0; i < mdxa->numBones; i++) + { + skel = (mdxaSkel_t *)((byte *)mdxa + sizeof(mdxaHeader_t) + offsets->offsets[i]); + if (!Q_stricmp(skel->name, boneName)) + { //got it + return qtrue; + } + } + } + } + + //guess it doesn't exist + return qfalse; +} + +//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 + +qboolean G2API_SetBoneAnimIndex(CGhoul2Info *ghlInfo, const int index, const int AstartFrame, const int AendFrame, const int flags, const float animSpeed, const int currentTime, const float AsetFrame, const int blendTime) +{ + qboolean setPtrs = qfalse; + qboolean res = qfalse; + + //rww - RAGDOLL_BEGIN + if (ghlInfo) + { + res = G2_SetupModelPointers(ghlInfo); + setPtrs = qtrue; + + if (res) + { + if (ghlInfo->mFlags & GHOUL2_RAG_STARTED) + { + return qfalse; + } + } + } + //rww - RAGDOLL_END + + int endFrame=AendFrame; + int startFrame=AstartFrame; + float setFrame=AsetFrame; + assert(endFrame>0); + assert(startFrame>=0); + assert(endFrame<100000); + assert(startFrame<100000); + assert(setFrame>=0.0f||setFrame==-1.0f); + assert(setFrame<=100000.0f); + if (endFrame<=0) + { + endFrame=1; + } + if (endFrame>=100000) + { + endFrame=1; + } + if (startFrame<0) + { + startFrame=0; + } + if (startFrame>=100000) + { + startFrame=0; + } + if (setFrame<0.0f&&setFrame!=-1.0f) + { + setFrame=0.0f; + } + if (setFrame>100000.0f) + { + setFrame=0.0f; + } + if (!setPtrs) + { + res = G2_SetupModelPointers(ghlInfo); + } + + if (res) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Set_Bone_Anim_Index(ghlInfo->mBlist, index, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime, ghlInfo->aHeader->numFrames); + } + return qfalse; +} + +#define _PLEASE_SHUT_THE_HELL_UP + +qboolean G2API_SetBoneAnim(CGhoul2Info_v &ghoul2, const int modelIndex, const char *boneName, const int AstartFrame, const int AendFrame, const int flags, const float animSpeed, const int currentTime, const float AsetFrame, const int blendTime) +{ + int endFrame=AendFrame; + int startFrame=AstartFrame; + float setFrame=AsetFrame; +#ifndef _PLEASE_SHUT_THE_HELL_UP + assert(endFrame>0); + assert(startFrame>=0); + assert(endFrame<100000); + assert(startFrame<100000); + assert(setFrame>=0.0f||setFrame==-1.0f); + assert(setFrame<=100000.0f); +#endif + if (endFrame<=0) + { + endFrame=1; + } + if (endFrame>=100000) + { + endFrame=1; + } + if (startFrame<0) + { + startFrame=0; + } + if (startFrame>=100000) + { + startFrame=0; + } + if (setFrame<0.0f&&setFrame!=-1.0f) + { + setFrame=0.0f; + } + if (setFrame>100000.0f) + { + setFrame=0.0f; + } + if ((int)&ghoul2 && ghoul2.size()>modelIndex) + { + CGhoul2Info *ghlInfo = &ghoul2[modelIndex]; + qboolean setPtrs = qfalse; + qboolean res = qfalse; + + //rww - RAGDOLL_BEGIN + if (ghlInfo) + { + res = G2_SetupModelPointers(ghlInfo); + setPtrs = qtrue; + + if (res) + { + if (ghlInfo->mFlags & GHOUL2_RAG_STARTED) + { + return qfalse; + } + } + } + //rww - RAGDOLL_END + + if (!setPtrs) + { + res = G2_SetupModelPointers(ghlInfo); + } + + if (res) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Set_Bone_Anim(ghlInfo, ghlInfo->mBlist, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime); + } + } + return qfalse; +} + +qboolean G2API_GetBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList) +{ + assert(startFrame!=endFrame); //this is bad + assert(startFrame!=flags); //this is bad + assert(endFrame!=flags); //this is bad + assert(currentFrame!=animSpeed); //this is bad + if (G2_SetupModelPointers(ghlInfo)) + { + int aCurrentTime=G2API_GetTime(currentTime); + qboolean ret=G2_Get_Bone_Anim(ghlInfo, ghlInfo->mBlist, boneName, aCurrentTime, currentFrame, + startFrame, endFrame, flags, animSpeed, modelList, ghlInfo->mModelindex); +#ifdef _DEBUG + /* + assert(*endFrame>0); + assert(*endFrame<100000); + assert(*startFrame>=0); + assert(*startFrame<100000); + assert(*currentFrame>=0.0f); + assert(*currentFrame<100000.0f); + */ + if (*endFrame<1) + { + *endFrame=1; + } + if (*endFrame>100000) + { + *endFrame=1; + } + if (*startFrame<0) + { + *startFrame=0; + } + if (*startFrame>100000) + { + *startFrame=1; + } + if (*currentFrame<0.0f) + { + *currentFrame=0.0f; + } + if (*currentFrame>100000) + { + *currentFrame=1; + } +#endif + return ret; + } + return qfalse; +} + +qboolean G2API_GetAnimRange(CGhoul2Info *ghlInfo, const char *boneName, int *startFrame, int *endFrame) +{ + assert(startFrame!=endFrame); //this is bad + if (G2_SetupModelPointers(ghlInfo)) + { + qboolean ret=G2_Get_Bone_Anim_Range(ghlInfo, ghlInfo->mBlist, boneName, startFrame, endFrame); +#ifdef _DEBUG + assert(*endFrame>0); + assert(*endFrame<100000); + assert(*startFrame>=0); + assert(*startFrame<100000); + if (*endFrame<1) + { + *endFrame=1; + } + if (*endFrame>100000) + { + *endFrame=1; + } + if (*startFrame<0) + { + *startFrame=0; + } + if (*startFrame>100000) + { + *startFrame=1; + } +#endif + return ret; + } + return qfalse; +} + + +qboolean G2API_PauseBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int currentTime) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_Pause_Bone_Anim(ghlInfo, ghlInfo->mBlist, boneName, currentTime); + } + return qfalse; +} + +qboolean G2API_IsPaused(CGhoul2Info *ghlInfo, const char *boneName) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_IsPaused(ghlInfo->mFileName, ghlInfo->mBlist, boneName); + } + return qfalse; +} + +qboolean G2API_StopBoneAnimIndex(CGhoul2Info *ghlInfo, const int index) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_Stop_Bone_Anim_Index(ghlInfo->mBlist, index); + } + return qfalse; +} + +qboolean G2API_StopBoneAnim(CGhoul2Info *ghlInfo, const char *boneName) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_Stop_Bone_Anim(ghlInfo->mFileName, ghlInfo->mBlist, boneName); + } + return qfalse; +} + +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) +{ + qboolean setPtrs = qfalse; + qboolean res = qfalse; + + //rww - RAGDOLL_BEGIN + if (ghlInfo) + { + res = G2_SetupModelPointers(ghlInfo); + setPtrs = qtrue; + + if (res) + { + if (ghlInfo->mFlags & GHOUL2_RAG_STARTED) + { + return qfalse; + } + } + } + //rww - RAGDOLL_END + + if (!setPtrs) + { + res = G2_SetupModelPointers(ghlInfo); + } + + if (res) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Set_Bone_Angles_Index( ghlInfo->mBlist, index, angles, flags, yaw, pitch, roll, modelList, ghlInfo->mModelindex, blendTime, currentTime); + } + return qfalse; +} + +qboolean G2API_SetBoneAngles(CGhoul2Info_v &ghoul2, const int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const Eorientations up, const Eorientations left, const Eorientations forward, + qhandle_t *modelList, int blendTime, int currentTime ) +{ + if ((int)&ghoul2 && ghoul2.size()>modelIndex) + { + CGhoul2Info *ghlInfo = &ghoul2[modelIndex]; + qboolean setPtrs = qfalse; + qboolean res = qfalse; + + //rww - RAGDOLL_BEGIN + if (ghlInfo) + { + res = G2_SetupModelPointers(ghlInfo); + setPtrs = qtrue; + + if (res) + { + if (ghlInfo->mFlags & GHOUL2_RAG_STARTED) + { + return qfalse; + } + } + } + //rww - RAGDOLL_END + + if (!setPtrs) + { + res = G2_SetupModelPointers(ghoul2); + } + + if (res) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Set_Bone_Angles(ghlInfo, ghlInfo->mBlist, boneName, angles, flags, up, left, forward, modelList, ghlInfo->mModelindex, blendTime, currentTime); + } + } + return qfalse; +} + +qboolean G2API_SetBoneAnglesMatrixIndex(CGhoul2Info *ghlInfo, const int index, const mdxaBone_t &matrix, + const int flags, qhandle_t *modelList, int blendTime, int currentTime) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Set_Bone_Angles_Matrix_Index(ghlInfo->mBlist, index, matrix, flags, modelList, ghlInfo->mModelindex, blendTime, currentTime); + } + return qfalse; +} + +qboolean G2API_SetBoneAnglesMatrix(CGhoul2Info *ghlInfo, const char *boneName, const mdxaBone_t &matrix, + const int flags, qhandle_t *modelList, int blendTime, int currentTime) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Set_Bone_Angles_Matrix(ghlInfo->mFileName, ghlInfo->mBlist, boneName, matrix, flags, modelList, ghlInfo->mModelindex, blendTime, currentTime); + } + return qfalse; +} + +qboolean G2API_StopBoneAnglesIndex(CGhoul2Info *ghlInfo, const int index) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Stop_Bone_Angles_Index(ghlInfo->mBlist, index); + } + return qfalse; +} + +qboolean G2API_StopBoneAngles(CGhoul2Info *ghlInfo, const char *boneName) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Stop_Bone_Angles(ghlInfo->mFileName, ghlInfo->mBlist, boneName); + } + return qfalse; +} + + +void G2API_AbsurdSmoothing(CGhoul2Info_v &ghoul2, qboolean status) +{ + assert(ghoul2.size()); + CGhoul2Info *ghlInfo = &ghoul2[0]; + + if (status) + { //turn it on + ghlInfo->mFlags |= GHOUL2_CRAZY_SMOOTH; + } + else + { //off + ghlInfo->mFlags &= ~GHOUL2_CRAZY_SMOOTH; + } +} + +//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); +} + +void G2_ResetRagDoll(CGhoul2Info_v &ghoul2V); +void G2API_ResetRagDoll(CGhoul2Info_v &ghoul2) +{ + G2_ResetRagDoll(ghoul2); +} +//rww - RAGDOLL_END + +qboolean G2API_RemoveBone(CGhoul2Info *ghlInfo, const char *boneName) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + // ensure we flush the cache + ghlInfo->mSkelFrameNum = 0; + return G2_Remove_Bone(ghlInfo, ghlInfo->mBlist, boneName); + } + return qfalse; +} + +//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 + /* + if (ragTraceTime) + { + Com_Printf("Rag trace time: %i (%i STARTSOLID, %i TOTAL)\n", ragTraceTime, ragSSCount, ragTraceCount); + } + */ + + //keep sane limits here, if it gets too slow an assert is proper. +// assert(ragTraceTime < 150); +// assert(ragTraceCount < 1500); +#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) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_Remove_Bolt( ghlInfo->mBltlist, index); + } + return qfalse; +} + +int G2API_AddBolt(CGhoul2Info_v &ghoul2, const int modelIndex, const char *boneName) +{ + assert(ghoul2.size()>modelIndex); + + if ((int)&ghoul2 && ghoul2.size()>modelIndex) + { + CGhoul2Info *ghlInfo = &ghoul2[modelIndex]; + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_Add_Bolt(ghlInfo, ghlInfo->mBltlist, ghlInfo->mSlist, boneName); + } + } + return -1; +} + +int G2API_AddBoltSurfNum(CGhoul2Info *ghlInfo, const int surfIndex) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_Add_Bolt_Surf_Num(ghlInfo, ghlInfo->mBltlist, ghlInfo->mSlist, surfIndex); + } + return -1; +} + + +qboolean G2API_AttachG2Model(CGhoul2Info_v &ghoul2From, int modelFrom, CGhoul2Info_v &ghoul2To, int toBoltIndex, int toModel) +{ + assert( toBoltIndex >= 0 ); + if ( toBoltIndex < 0 ) + { + return qfalse; + } + if (G2_SetupModelPointers(ghoul2From)&&G2_SetupModelPointers(ghoul2To)) + { + // make sure we have a model to attach, a model to attach to, and a bolt on that model + if (((int)&ghoul2From) && + ((int)&ghoul2To) && + (ghoul2From.size() > modelFrom) && + (ghoul2To.size() > toModel) && + ((ghoul2To[toModel].mBltlist[toBoltIndex].boneNumber != -1) || (ghoul2To[toModel].mBltlist[toBoltIndex].surfaceNumber != -1))) + { + // encode the bolt address into the model bolt link + toModel &= MODEL_AND; + toBoltIndex &= BOLT_AND; + ghoul2From[modelFrom].mModelBoltLink = (toModel << MODEL_SHIFT) | (toBoltIndex << BOLT_SHIFT); + return qtrue; + } + } + return qfalse; +} + +void G2API_SetBoltInfo(CGhoul2Info_v &ghoul2, int modelIndex, int boltInfo) +{ + if ((int)&ghoul2) + { + if (ghoul2.size() > modelIndex) + { + ghoul2[modelIndex].mModelBoltLink = boltInfo; + } + } +} + +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) +{ + 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); + return qtrue; + } + } + return qfalse; + +} + +qboolean gG2_GBMNoReconstruct; +qboolean gG2_GBMUseSPMethod; + +qboolean G2API_GetBoltMatrix_SPMethod(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 ) +{ + assert(ghoul2.size() > modelIndex); + + if ((int)&ghoul2 && (ghoul2.size() > modelIndex)) + { + CGhoul2Info *ghlInfo = &ghoul2[modelIndex]; + + //assert(boltIndex < ghlInfo->mBltlist.size()); + + if (ghlInfo && (boltIndex < ghlInfo->mBltlist.size()) && boltIndex >= 0 ) + { + // make sure we have transformed the skeleton + if (!gG2_GBMNoReconstruct) + { + G2_ConstructGhoulSkeleton(ghoul2, frameNum, true, scale); + } + + gG2_GBMNoReconstruct = qfalse; + + mdxaBone_t scaled; + mdxaBone_t *use; + use=&ghlInfo->mBltlist[boltIndex].position; + + if (scale[0]||scale[1]||scale[2]) + { + scaled=*use; + use=&scaled; + + // scale the bolt position by the scale factor for this model since at this point its still in model space + if (scale[0]) + { + scaled.matrix[0][3] *= scale[0]; + } + if (scale[1]) + { + scaled.matrix[1][3] *= scale[1]; + } + if (scale[2]) + { + scaled.matrix[2][3] *= scale[2]; + } + } + // pre generate the world matrix + G2_GenerateWorldMatrix(angles, position); + + VectorNormalize((float*)use->matrix[0]); + VectorNormalize((float*)use->matrix[1]); + VectorNormalize((float*)use->matrix[2]); + + Multiply_3x4Matrix(matrix, &worldMatrix, use); + return qtrue; + } + } + return qfalse; +} + +#define G2ERROR(exp,m) ((void)0) //rwwFIXMEFIXME: This is because I'm lazy. +#define G2WARNING(exp,m) ((void)0) +#define G2NOTE(exp,m) ((void)0) +#define G2ANIM(ghlInfo,m) ((void)0) +bool G2_NeedsRecalc(CGhoul2Info *ghlInfo,int frameNum); +void G2_GetBoltMatrixLow(CGhoul2Info &ghoul2,int boltNum,const vec3_t scale,mdxaBone_t &retMatrix); +void G2_GetBoneMatrixLow(CGhoul2Info &ghoul2,int boneNum,const vec3_t scale,mdxaBone_t &retMatrix,mdxaBone_t *&retBasepose,mdxaBone_t *&retBaseposeInv); + +//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 ) +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, 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 0 //yeah, screw it + if (!gG2_GBMNoReconstruct) + { //This should only be used when you know what you're doing. + if (G2_NeedsRecalc(ghlInfo,tframeNum)) + { + G2_ConstructGhoulSkeleton(ghoul2,tframeNum,true,scale); + } + } + else + { + gG2_GBMNoReconstruct = qfalse; + } +#else + if (G2_NeedsRecalc(ghlInfo,tframeNum)) + { + G2_ConstructGhoulSkeleton(ghoul2,tframeNum,true,scale); + } +#endif + + 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"); + + if (!gG2_GBMUseSPMethod) + { //this is horribly stupid and I hate it. But lots of game code is written to assume this 90 degree offset thing. + mdxaBone_t rotMat, tempMatrix; + vec3_t newangles = {0,270,0}; + Create_Matrix(newangles, &rotMat); + // make the model space matrix we have for this bolt into a world matrix + Multiply_3x4Matrix(&tempMatrix, &worldMatrix, &bolt); + vec3_t origin; + origin[0] = tempMatrix.matrix[0][3]; + origin[1] = tempMatrix.matrix[1][3]; + origin[2] = tempMatrix.matrix[2][3]; + tempMatrix.matrix[0][3] = tempMatrix.matrix[1][3] = tempMatrix.matrix[2][3] = 0; + Multiply_3x4Matrix(matrix, &tempMatrix, &rotMat); + matrix->matrix[0][3] = origin[0]; + matrix->matrix[1][3] = origin[1]; + matrix->matrix[2][3] = origin[2]; + } + else + { //reset it + gG2_GBMUseSPMethod = qfalse; + } + + return qtrue; + } + } + } + else + { + G2WARNING(0,"G2API_GetBoltMatrix Failed on empty or bad model"); + } + Multiply_3x4Matrix(matrix, &worldMatrix, (mdxaBone_t *)&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) +{ + int i; + if ((int)&ghoul2) + { + for (i=0; imdxm->animName; +} + +/************************************************************************************************ + * 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) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_GetAnimFileName(ghlInfo->mFileName, filename); + } + return qfalse; +} + +/* +======================= +SV_QsortEntityNumbers +======================= +*/ +static int QDECL QsortDistance( const void *a, const void *b ) { + const float &ea = ((CollisionRecord_t*)a)->mDistance; + const float &eb = ((CollisionRecord_t*)b)->mDistance; + + if ( ea < eb ) { + return -1; + } + return 1; +} + + +void G2API_CollisionDetect(CollisionRecord_t *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, int traceFlags, int useLod, float fRadius) +{ + if (G2_SetupModelPointers(ghoul2)) + { + 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); + + G2VertSpace->ResetHeap(); + + // now having done that, time to build the model +#ifdef _G2_GORE + G2_TransformModel(ghoul2, frameNumber, scale, G2VertSpace, useLod, false); +#else + G2_TransformModel(ghoul2, frameNumber, scale, G2VertSpace, 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, traceFlags, useLod, fRadius,0,0,0,0,0); +#else + G2_TraceModels(ghoul2, transRayStart, transRayEnd, collRecMap, entNum, traceFlags, useLod, fRadius); +#endif + int i; + for ( i = 0; i < MAX_G2_COLLISIONS && collRecMap[i].mEntityNum != -1; i ++ ); + + // now sort the resulting array of collision records so they are distance ordered + qsort( collRecMap, i, + sizeof( CollisionRecord_t ), QsortDistance ); + } +} + +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; + } +} + + +int G2API_CopyGhoul2Instance(CGhoul2Info_v &g2From, CGhoul2Info_v &g2To, 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 (g2From.IsValid()) + { +#ifdef _DEBUG + if (g2To.IsValid()) + { + assert(!"Copying to a valid g2 instance?!"); + + if (g2To[0].mBoneCache) + { + assert(!"Instance has a bonecache too.. it's gonna get stomped"); + } + } +#endif + g2To.DeepCopy(g2From); + +#ifdef _G2_GORE //check through gore stuff then, as well. + int model = 0; + + while (model < g2To.size()) + { + if ( g2To[model].mGoreSetTag ) + { + CGoreSet* gore = FindGoreSet ( g2To[model].mGoreSetTag ); + assert(gore); + gore->mRefCount++; + } + + model++; + } +#endif + //G2ANIM(ghoul2From,"G2API_CopyGhoul2Instance (source)"); + //G2ANIM(ghoul2To,"G2API_CopyGhoul2Instance (dest)"); + } + + return -1; +} + +void G2API_CopySpecificG2Model(CGhoul2Info_v &ghoul2From, int modelFrom, CGhoul2Info_v &ghoul2To, int modelTo) +{ +#if 0 + qboolean forceReconstruct = qtrue; +#endif //model1 was not getting reconstructed like it should for thrown sabers? + //might have been a bug in the reconstruct checking which has since been + //mangled and probably fixed. -rww + + // have we real ghoul2 models yet? + if (((int)&ghoul2From) && ((int)&ghoul2To)) + { + // assume we actually have a model to copy from + if (ghoul2From.size() > modelFrom) + { + // if we don't have enough models on the to side, resize us so we do + if (ghoul2To.size() <= modelTo) + { + assert (modelTo < 5); + ghoul2To.resize(modelTo + 1); +#if 0 + forceReconstruct = qtrue; +#endif + } + // do the copy + + if (ghoul2To.IsValid() && ghoul2To.size() >= modelTo) + { //remove the bonecache before we stomp over this instance. + if (ghoul2To[modelTo].mBoneCache) + { + RemoveBoneCache(ghoul2To[modelTo].mBoneCache); + ghoul2To[modelTo].mBoneCache = 0; + } + } + ghoul2To[modelTo] = ghoul2From[modelFrom]; + +#if 0 + if (forceReconstruct) + { //rww - we should really do this shouldn't we? If we don't mark a reconstruct after this, + //and we do a GetBoltMatrix in the same frame, it doesn't reconstruct the skeleton and returns + //a completely invalid matrix + ghoul2To[0].mSkelFrameNum = 0; + } +#endif + } + } +} + +// This version will automatically copy everything about this model, and make a new one if necessary. +void G2API_DuplicateGhoul2Instance(CGhoul2Info_v &g2From, CGhoul2Info_v **g2To) +{ + int ignore; + + if (*g2To) + { // This is bad. We only want to do this if there is not yet a to declared. + assert(0); + return; + } + + *g2To = new CGhoul2Info_v; +#ifdef _FULL_G2_LEAK_CHECKING + if (g_G2AllocServer) + { + g_G2ServerAlloc += sizeof(CGhoul2Info_v); + } + else + { + g_G2ClientAlloc += sizeof(CGhoul2Info_v); + } + g_Ghoul2Allocations += sizeof(CGhoul2Info_v); + G2_DEBUG_ShovePtrInTracker(*g2To); +#endif + CGhoul2Info_v &ghoul2 = *(*g2To); + + ignore = G2API_CopyGhoul2Instance(g2From, ghoul2, -1); + + return; +} + +char *G2API_GetSurfaceName(CGhoul2Info *ghlInfo, int surfNumber) +{ + static char noSurface[1] = ""; + if (G2_SetupModelPointers(ghlInfo)) + { + model_t *mod = (model_t *)ghlInfo->currentModel; + mdxmSurface_t *surf = 0; + mdxmSurfHierarchy_t *surfInfo = 0; + +#ifndef FINAL_BUILD + if (!mod || !mod->mdxm) + { + Com_Error(ERR_DROP, "G2API_GetSurfaceName: Bad model on instance %s.", ghlInfo->mFileName); + } +#endif + + //ok, I guess it's semi-valid for the user to be passing in surface > numSurfs because they don't know how many surfs a model + //may have.. but how did they get that surf index to begin with? Oh well. + if (surfNumber < 0 || surfNumber >= mod->mdxm->numSurfaces) + { + Com_Printf("G2API_GetSurfaceName: You passed in an invalid surface number (%i) for model %s.\n", surfNumber, ghlInfo->mFileName); + return noSurface; + } + + + surf = (mdxmSurface_t *)G2_FindSurface((void *)mod, surfNumber, 0); + if (surf) + { +#ifndef FINAL_BUILD + if (surf->thisSurfaceIndex < 0 || surf->thisSurfaceIndex >= mod->mdxm->numSurfaces) + { + Com_Error(ERR_DROP, "G2API_GetSurfaceName: Bad surf num (%i) on surf for instance %s.", surf->thisSurfaceIndex, ghlInfo->mFileName); + } +#endif + mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)mod->mdxm + sizeof(mdxmHeader_t)); + surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surf->thisSurfaceIndex]); + return surfInfo->name; + } + } + return noSurface; +} + + +int G2API_GetSurfaceIndex(CGhoul2Info *ghlInfo, const char *surfaceName) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_GetSurfaceIndex(ghlInfo, surfaceName); + } + return -1; +} + +char *G2API_GetGLAName(CGhoul2Info_v &ghoul2, int modelIndex) +{ + if (G2_SetupModelPointers(ghoul2)) + { + if (((int)&ghoul2) && (ghoul2.size() > modelIndex)) + { + //model_t *mod = R_GetModelByHandle(RE_RegisterModel(ghoul2[modelIndex].mFileName)); + //return mod->mdxm->animName; + + assert(ghoul2[modelIndex].currentModel && ghoul2[modelIndex].currentModel->mdxm); + return ghoul2[modelIndex].currentModel->mdxm->animName; + } + } + return NULL; +} + +qboolean G2API_SetNewOrigin(CGhoul2Info_v &ghoul2, const int boltIndex) +{ + CGhoul2Info *ghlInfo = NULL; + + if ((int)&ghoul2 && ghoul2.size()>0) + { + ghlInfo = &ghoul2[0]; + } + + if (G2_SetupModelPointers(ghlInfo)) + { + if (boltIndex < 0) + { + char modelName[MAX_QPATH]; + if (ghlInfo->currentModel && + ghlInfo->currentModel->name && + ghlInfo->currentModel->name[0]) + { + strcpy(modelName, ghlInfo->currentModel->name); + } + else + { + strcpy(modelName, "None?!"); + } + + Com_Error(ERR_DROP, "Bad boltindex (%i) trying to SetNewOrigin (naughty naughty!)\nModel %s\n", boltIndex, modelName); + } + + ghlInfo->mNewOrigin = boltIndex; + ghlInfo->mFlags |= GHOUL2_NEWORIGIN; + return qtrue; + } + return qfalse; +} + +int G2API_GetBoneIndex(CGhoul2Info *ghlInfo, const char *boneName) +{ + if (G2_SetupModelPointers(ghlInfo)) + { + return G2_Get_Bone_Index(ghlInfo, boneName); + } + return -1; +} + +qboolean G2API_SaveGhoul2Models(CGhoul2Info_v &ghoul2, char **buffer, int *size) +{ + return G2_SaveGhoul2Models(ghoul2, buffer, size); +} + +void G2API_LoadGhoul2Models(CGhoul2Info_v &ghoul2, char *buffer) +{ + G2_LoadGhoul2Model(ghoul2, buffer); +} + +void G2API_FreeSaveBuffer(char *buffer) +{ + Z_Free(buffer); +} + +// 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) +{ + +#ifdef _G2_GORE + G2API_ClearSkinGore ( ghoul2 ); +#endif + ghoul2.~CGhoul2Info_v(); // so I can load junk over it then memset to 0 without orphaning +} + +//see if surfs have any shader info... +qboolean G2API_SkinlessModel(CGhoul2Info *g2) +{ + if (G2_SetupModelPointers(g2)) + { + model_t *mod = (model_t *)g2->currentModel; + + if (mod && + mod->mdxm) + { + mdxmSurfHierarchy_t *surf; + int i; + + surf = (mdxmSurfHierarchy_t *) ( (byte *)mod->mdxm + mod->mdxm->ofsSurfHierarchy ); + + for (i = 0; i < mod->mdxm->numSurfaces; i++) + { + if (surf->shader && surf->shader[0]) + { //found a surface with a shader name, ok. + return qfalse; + } + + surf = (mdxmSurfHierarchy_t *)( (byte *)surf + (int)( &((mdxmSurfHierarchy_t *)0)->childIndexes[ surf->numChildren ] )); + } + } + } + + //found nothing. + return qtrue; +} + +//#ifdef _SOF2 +#ifdef _G2_GORE +void ResetGoreTag(); // put here to reduce coupling + +//way of seeing how many marks are on a model currently -rww +int G2API_GetNumGoreMarks(CGhoul2Info *g2) +{ + if (g2->mGoreSetTag) + { + CGoreSet *goreSet = FindGoreSet(g2->mGoreSetTag); + + if (goreSet) + { + return goreSet->mGoreRecords.size(); + } + } + + return 0; +} + +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, 0,lod,0.0f,gore.SSize,gore.TSize,gore.theta,gore.shader,&gore); + } +} +#endif + +qboolean G2_TestModelPointers(CGhoul2Info *ghlInfo) // returns true if the model is properly set up +{ + G2ERROR(ghlInfo,"NULL ghlInfo"); + if (!ghlInfo) + { + return qfalse; + } + ghlInfo->mValid=false; + if (ghlInfo->mModelindex != -1) + { + if ((com_dedicated && com_dedicated->integer) || + (G2_ShouldRegisterServer())) //supreme hackery! + { + ghlInfo->mModel = RE_RegisterServerModel(ghlInfo->mFileName); + } + else + { + 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); + if (ghlInfo->animModel) + { + ghlInfo->aHeader =ghlInfo->animModel->mdxa; + if (ghlInfo->aHeader) + { + 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 (qboolean)ghlInfo->mValid; +} + +#ifdef G2_PERFORMANCE_ANALYSIS +#include "../qcommon/timing.h" +extern timing_c G2PerformanceTimer_G2_SetupModelPointers; +extern int G2Time_G2_SetupModelPointers; +#endif + +qboolean G2_SetupModelPointers(CGhoul2Info *ghlInfo) // returns true if the model is properly set up +{ +#ifdef G2_PERFORMANCE_ANALYSIS + G2PerformanceTimer_G2_SetupModelPointers.Start(); +#endif + G2ERROR(ghlInfo,"NULL ghlInfo"); + if (!ghlInfo) + { + return qfalse; + } + +// if (ghlInfo->mValid && ghlInfo->currentModel) + if (0) + { //rww - Why are we bothering with all this? We can't change models like this anyway. + //This function goes over 200k on the precision timer (in debug, but still), so I'm + //cutting it off here because it gets called constantly. +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_G2_SetupModelPointers += G2PerformanceTimer_G2_SetupModelPointers.End(); +#endif + return qtrue; + } + + 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"); + + // RJ - experimental optimization! + if (!ghlInfo->mModel || 1) + { + if ((com_dedicated && com_dedicated->integer) || + (G2_ShouldRegisterServer())) //supreme hackery! + { + ghlInfo->mModel = RE_RegisterServerModel(ghlInfo->mFileName); + } + else + { + 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); + 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) + { + 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; + } + +#ifdef G2_PERFORMANCE_ANALYSIS + G2Time_G2_SetupModelPointers += G2PerformanceTimer_G2_SetupModelPointers.End(); +#endif + return (qboolean)ghlInfo->mValid; +} + +qboolean G2_SetupModelPointers(CGhoul2Info_v &ghoul2) // returns true if any model is properly set up +{ + bool ret=false; + int i; + for (i=0; imValid); + boltInfo_t tempBolt; + int i; + + // first up, make sure have a surface first + if (surfNum >= slist.size()) + { + return -1; + } + + // look through entire list - see if it's already there first + for(i=0; imValid); + model_t *mod_m = (model_t *)ghlInfo->currentModel; + model_t *mod_a = (model_t *)ghlInfo->animModel; + int i, x, surfNum = -1; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + mdxmHierarchyOffsets_t *surfOffsets; + boltInfo_t tempBolt; + int flags; + + surfOffsets = (mdxmHierarchyOffsets_t *)((byte*)mod_m->mdxm + sizeof(mdxmHeader_t)); + // first up, we'll search for that which this bolt names in all the surfaces + surfNum = G2_IsSurfaceLegal((void*)mod_m, 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; imdxa + 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_a->mdxa->numBones; x++) + { + skel = (mdxaSkel_t *)((byte *)mod_a->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_a->mdxa->numBones) + { + // didn't find it? Error + //assert(0&&x == mod_a->mdxa->numBones); +#ifdef _DEBUG +// Com_Printf("WARNING: %s not found on skeleton\n", boneName); +#endif + return -1; + } + + // look through entire list - see if it's already there first + for(i=0; i-1; i--) + { + if ((bltlist[i].surfaceNumber == -1) && (bltlist[i].boneNumber == -1)) + { + newSize = i; + } + // once we hit one that isn't a -1, we are done. + else + { + break; + } + } + // do we need to resize? + if (newSize != bltlist.size()) + { + // yes, so lets do it + bltlist.resize(newSize); + } + + } + return qtrue; + } + + assert(0); + + // no + return qfalse; +} + +// set the bolt list to all unused so the bone transformation routine ignores it. +void G2_Init_Bolt_List(boltInfo_v &bltlist) +{ + bltlist.clear(); +} + +// remove any bolts that reference original surfaces, generated surfaces, or bones that aren't active anymore +void G2_RemoveRedundantBolts(boltInfo_v &bltlist, surfaceInfo_v &slist, int *activeSurfaces, int *activeBones) +{ + // walk the bolt list + for (int i=0; i +#else +#include +#define _isnan isnan +#endif +#include "G2_gore.h" + +//#define RAG_TRACE_DEBUG_LINES + +#include "../client/client.h" //while this is all "shared" code, there are some places where we want to make cgame callbacks (for ragdoll) only if the cgvm exists +//rww - RAGDOLL_END + +//===================================================================================================================== +// 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(const model_t *mod, boneInfo_v &blist, const char *boneName) +{ + int i; + mdxaSkel_t *skel; + mdxaSkelOffsets_t *offsets; + offsets = (mdxaSkelOffsets_t *)((byte *)mod->mdxa + sizeof(mdxaHeader_t)); + skel = (mdxaSkel_t *)((byte *)mod->mdxa + sizeof(mdxaHeader_t) + offsets->offsets[0]); + + // look through entire list + 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)) + { + return i; + } + } + + // didn't find it + return -1; +} + +// 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) + { + // didn't find it? Error + //assert(0); +#ifdef _DEBUG + Com_Printf("WARNING: Failed to add bone %s\n", boneName); +#endif + +#ifdef _RAG_PRINT_TEST + Com_Printf("WARNING: Failed to add bone %s\n", boneName); +#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)) + { + 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; + return i; + } + } + +#ifdef _RAG_PRINT_TEST + Com_Printf("New bone added for %s\n", boneName); +#endif + // 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); + 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) +{ + if (index != -1) + { + if (blist[index].flags & BONE_ANGLES_RAGDOLL) + { + return qtrue; // don't accept any calls on ragdoll bones + } + } + + // 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; + + unsigned int newSize = blist.size(); + // now look through the list from the back and see if there is a block of -1's we can resize off the end of the list + for (int i=blist.size()-1; i>-1; i--) + { + if (blist[i].boneNumber == -1) + { + newSize = i; + } + // once we hit one that isn't a -1, we are done. + else + { + break; + } + } + // do we need to resize? + if (newSize != blist.size()) + { + // yes, so lets do it + blist.resize(newSize); + } + + return qtrue; + } + } + +// assert(0); + // no + 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; + + assert(ghlInfo->animModel); + index = G2_Find_Bone(ghlInfo->animModel, blist, boneName); + + 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( boneInfo_v &blist, const int index, + const float *angles, const int flags, const Eorientations yaw, + const Eorientations pitch, const Eorientations roll, qhandle_t *modelList, + const int modelIndex, const int blendTime, const int currentTime) +{ + if ((index >= blist.size()) || (blist[index].boneNumber == -1)) + { + // we are attempting to set a bone override that doesn't exist + assert(0); + return qfalse; + } + + if (index != -1) + { + if (blist[index].flags & BONE_ANGLES_RAGDOLL) + { + return qtrue; // don't accept any calls on ragdoll bones + } + } + + if (flags & (BONE_ANGLES_PREMULT | BONE_ANGLES_POSTMULT)) + { + // you CANNOT call this with an index with these kinds of bone overrides - we need the model details for these kinds of bone angle overrides + 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; +#if DEBUG_PCJ + OutputDebugString(va("PCJ %2d %6d (%6.2f,%6.2f,%6.2f) %d %d %d %d\n",index,currentTime,angles[0],angles[1],angles[2],yaw,pitch,roll,flags)); +#endif + + G2_Generate_Matrix(NULL, 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, + qhandle_t *modelList, const int modelIndex, const int blendTime, const int currentTime) +{ + model_t *mod_m; + model_t *mod_a; + + mod_m = (model_t *)ghlInfo->currentModel; + mod_a = (model_t *)ghlInfo->animModel; + + int index = G2_Find_Bone(mod_a, blist, boneName); + + // did we find it? + if (index != -1) + { + if (blist[index].flags & BONE_ANGLES_RAGDOLL) + { + return qtrue; // don't accept any calls on ragdoll bones + } + + // 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("%2d %6d (%6.2f,%6.2f,%6.2f) %d %d %d %d\n",index,currentTime,angles[0],angles[1],angles[2],up,left,forward,flags)); +#endif + + G2_Generate_Matrix(mod_a, blist, index, angles, flags, up, left, forward); + return qtrue; + } + + // no - lets try and add this bone in + index = G2_Add_Bone(mod_a, blist, boneName); + + // did we find a free one? + if (index != -1) + { + // 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("%2d %6d (%6.2f,%6.2f,%6.2f) %d %d %d %d\n",index,currentTime,angles[0],angles[1],angles[2],up,left,forward,flags)); +#endif + + G2_Generate_Matrix(mod_a, blist, index, angles, flags, up, left, forward); + return qtrue; + } +// assert(0); + //Jeese, we don't need an assert here too. There's already a warning in G2_Add_Bone if it fails. + + // no + 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, qhandle_t *modelList, + const int modelIndex, const int blendTime, const int currentTime) +{ + + if ((index >= blist.size()) || (blist[index].boneNumber == -1)) + { + // we are attempting to set a bone override that doesn't exist + assert(0); + return qfalse; + } + if (index != -1) + { + if (blist[index].flags & BONE_ANGLES_RAGDOLL) + { + return qtrue; // don't accept any calls on ragdoll bones + } + } + // 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(const char *fileName, boneInfo_v &blist, const char *boneName, const mdxaBone_t &matrix, + const int flags, qhandle_t *modelList, const int modelIndex, const int blendTime, const int currentTime) +{ + model_t *mod_m; + if (!fileName[0]) + { + mod_m = R_GetModelByHandle(modelList[modelIndex]); + } + else + { + mod_m = R_GetModelByHandle(RE_RegisterModel(fileName)); + } + model_t *mod_a = R_GetModelByHandle(mod_m->mdxm->animIndex); + int index = G2_Find_Bone(mod_a, blist, boneName); + + if (index != -1) + { + if (blist[index].flags & BONE_ANGLES_RAGDOLL) + { + return qtrue; // don't accept any calls on ragdoll bones + } + } + + // did we find it? + if (index != -1) + { + // yes, so set the angles and flags correctly + 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; + } + + // no - lets try and add this bone in + index = G2_Add_Bone(mod_a, blist, boneName); + + // did we find a free one? + if (index != -1) + { + // yes, so set the angles and flags correctly + 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; + } + assert(0); + + // no + 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 blendTime, + const int numFrames) +{ + int modFlags = flags; + + if ((index >= blist.size()) || (blist[index].boneNumber == -1)) + { + // we are attempting to set a bone override that doesn't exist + assert(0); + return qfalse; + } + + if (index != -1) + { + if (blist[index].flags & BONE_ANGLES_RAGDOLL) + { + return qtrue; // don't accept any calls on ragdoll bones + } + } + + if (setFrame != -1) + { + assert((setFrame >= startFrame) && (setFrame <= endFrame)); + } + if (flags & 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, NULL, 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 >= 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 = startFrame; + } + // if we intend to end this anim or freeze after this, then just keep on the last frame + else + { + // assert(endFrame>0); + if (endFrame <= 0) + { + blist[index].blendLerpFrame = 0; + } + else + { + blist[index].blendFrame = endFrame -1; + } + } + } + + // cope with if the lerp frame is actually off the end of the anim + if (blist[index].blendLerpFrame >= 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 = startFrame; + } + // if we intend to end this anim or freeze after this, then just keep on the last frame + else + { + // assert(endFrame>0); + if (endFrame <= 0) + { + blist[index].blendLerpFrame = 0; + } + else + { + blist[index].blendLerpFrame = 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; + modFlags &= ~(BONE_ANIM_BLEND); + } + } + 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; + // start up the animation:) + if (setFrame != -1) + { + blist[index].lastTime = blist[index].startTime = (currentTime - (((setFrame - (float)startFrame) * 50.0)/ animSpeed)); + } + else + { + blist[index].lastTime = blist[index].startTime = currentTime; + } + blist[index].flags &= ~(BONE_ANIM_TOTAL); + if (blist[index].flags < 0) + { + blist[index].flags = 0; + } + blist[index].flags |= modFlags; + +#if DEBUG_G2_TIMING + if (index==2) + { + 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 + + 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) +{ + model_t *mod_a = (model_t *)ghlInfo->animModel; + + int index = G2_Find_Bone(mod_a, blist, boneName); + if (index == -1) + { + index = G2_Add_Bone(mod_a, blist, boneName); + } + + if (index != -1) + { + if (blist[index].flags & BONE_ANGLES_RAGDOLL) + { + return qtrue; // don't accept any calls on ragdoll bones + } + } + + if (index != -1) + { + return G2_Set_Bone_Anim_Index(blist,index,startFrame,endFrame,flags,animSpeed,currentTime,setFrame,blendTime,ghlInfo->aHeader->numFrames); + } + return qfalse; +} + +qboolean G2_Get_Bone_Anim_Range(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName, int *startFrame, int *endFrame) +{ + model_t *mod_a = (model_t *)ghlInfo->animModel; + int index = G2_Find_Bone(mod_a, blist, boneName); + + // did we find it? + if (index != -1) + { + // are we an animating bone? + if (blist[index].flags & (BONE_ANIM_OVERRIDE_LOOP | BONE_ANIM_OVERRIDE)) + { + *startFrame = blist[index].startFrame; + *endFrame = blist[index].endFrame; + 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 +void G2_TimingModel(boneInfo_t &bone,int currentTime,int numFramesInFile,int ¤tFrame,int &newFrame,float &lerp); + +qboolean G2_Get_Bone_Anim_Index( boneInfo_v &blist, const int index, const int currentTime, + float *currentFrame, int *startFrame, int *endFrame, int *flags, float *retAnimSpeed, qhandle_t *modelList, 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 lcurrentFrame,newFrame; + float lerp; + G2_TimingModel(blist[index],currentTime,numFrames,lcurrentFrame,newFrame,lerp); + + *currentFrame =float(lcurrentFrame)+lerp; + *startFrame = blist[index].startFrame; + *endFrame = blist[index].endFrame; + *flags = blist[index].flags; + *retAnimSpeed = blist[index].animSpeed; + return qtrue; + } + } + *startFrame=0; + *endFrame=1; + *currentFrame=0.0f; + *flags=0; + *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, qhandle_t *modelList, int modelIndex) +{ + model_t *mod_a = (model_t *)ghlInfo->animModel; + + int index = G2_Find_Bone(mod_a, blist, boneName); + + if (index==-1) + { + index = G2_Add_Bone(mod_a, blist, boneName); + + if (index == -1) + { + return qfalse; + } + } + + assert(ghlInfo->aHeader); + + if (G2_Get_Bone_Anim_Index(blist, index, currentTime, currentFrame, startFrame, endFrame, flags, retAnimSpeed, modelList, 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(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName, const int currentTime) +{ + model_t *mod_a = (model_t *)ghlInfo->animModel; + + int index = G2_Find_Bone(mod_a, blist, boneName); + + // did we find it? + if (index != -1) + { + // are we pausing or un pausing? + if (blist[index].pauseTime) + { + int startFrame, endFrame, flags; + float currentFrame, animSpeed; + + // figure out what frame we are on now + G2_Get_Bone_Anim(ghlInfo, blist, boneName, blist[index].pauseTime, ¤tFrame, &startFrame, &endFrame, &flags, &animSpeed, NULL, 0); + // reset start time so we are actually on this frame right now + G2_Set_Bone_Anim(ghlInfo, blist, boneName, startFrame, endFrame, flags, animSpeed, currentTime, currentFrame, 0); + // no pausing anymore + blist[index].pauseTime = 0; + } + // ahh, just pausing, the easy bit + else + { + blist[index].pauseTime = currentTime; + } + + return qtrue; + } + assert(0); + + return qfalse; +} + +qboolean G2_IsPaused(const char *fileName, boneInfo_v &blist, const char *boneName) +{ + model_t *mod_m = R_GetModelByHandle(RE_RegisterModel(fileName)); + model_t *mod_a = R_GetModelByHandle(mod_m->mdxm->animIndex); + int index = G2_Find_Bone(mod_a, blist, boneName); + + // did we find it? + 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 >= 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_ANIM_TOTAL); + // try and remove this bone if we can + 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(const char *fileName, boneInfo_v &blist, const char *boneName) +{ + model_t *mod_m = R_GetModelByHandle(RE_RegisterModel(fileName)); + model_t *mod_a = R_GetModelByHandle(mod_m->mdxm->animIndex); + int index = G2_Find_Bone(mod_a, blist, boneName); + + // did we find it? + if (index != -1) + { + blist[index].flags &= ~(BONE_ANIM_TOTAL); + // try and remove this bone if we can + 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); + // try and remove this bone if we can + 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(const char *fileName, boneInfo_v &blist, const char *boneName) +{ + model_t *mod_m = R_GetModelByHandle(RE_RegisterModel(fileName)); + model_t *mod_a = R_GetModelByHandle(mod_m->mdxm->animIndex); + int index = G2_Find_Bone(mod_a, blist, boneName); + + // did we find it? + if (index != -1) + { + blist[index].flags &= ~(BONE_ANGLES_TOTAL); + // try and remove this bone if we can + return G2_Remove_Bone_Index(blist, index); + } + assert(0); + + return qfalse; +} + + +// actually walk the bone list and update each and every bone if we have ended an animation for them. +void G2_Animate_Bone_List(CGhoul2Info_v &ghoul2, const int currentTime, const int index ) +{ + int i; + boneInfo_v &blist = ghoul2[index].mBlist; + + // look through entire list + for(i=0; i 0.0f) && (newFrame_g > endFrame-1 )) || + ((animSpeed < 0.0f) && (newFrame_g < endFrame+1 ))) + { + // yep - decide what to do + if (blist[i].flags & BONE_ANIM_OVERRIDE_LOOP) + { + // get our new animation frame back within the bounds of the animation set + if (animSpeed < 0.0f) + { + if (newFrame_g <= endFrame+1) + { + newFrame_g=endFrame+fmod(newFrame_g-endFrame,animSize)-animSize; + } + } + else + { + if (newFrame_g >= endFrame) + { + newFrame_g=endFrame+fmod(newFrame_g-endFrame,animSize)-animSize; + } + } + // figure out new start time + float frameTime = newFrame_g - blist[i].startFrame ; + blist[i].startTime = currentTime - (int)((frameTime / animSpeed) * 50.0f); + if (blist[i].startTime>currentTime) + { + blist[i].startTime=currentTime; + } + assert(blist[i].startTime <= currentTime); + blist[i].lastTime = blist[i].startTime; + } + else + { + if ((blist[i].flags & BONE_ANIM_OVERRIDE_FREEZE) != BONE_ANIM_OVERRIDE_FREEZE) + { + // nope, just stop it. And remove the bone if possible + G2_Stop_Bone_Index(blist, i, (BONE_ANIM_TOTAL)); + } + } + } + } + } + } + } +} + +//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; // 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=qfalse; + } + 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"); +#ifndef DEDICATED + 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; + } +#endif //!DEDICATED + 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=qtrue; + + G2_GenerateWorldMatrix(parms->angles, parms->position); + G2_ConstructGhoulSkeleton(ghoul2V, curTime, false, parms->scale); + + 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); + + //int startFrame = 3665, endFrame = 3665+1; + int startFrame = parms->startFrame, endFrame = parms->endFrame; + + 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); + + 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); + +#if 1 + // 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); +#else + // old base anim + int pcjflags=RAG_PCJ|RAG_PCJ_POST_MULT|RAG_EFFECTOR; + + VectorSet(pcjMin,-15.0f,-15.0f,-15.0f); + VectorSet(pcjMax,45.0f,15.0f,15.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lower_lumbar",pcjflags,10.0f,pcjMin,pcjMax,500); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"upper_lumbar",pcjflags,10.0f,pcjMin,pcjMax,500); + 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,"thoracic",pcjflags,10.0f,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,6.0f,pcjMin,pcjMax,500); + + //VectorSet(pcjMin,-45.0f,-90.0f,-100.0f); + VectorSet(pcjMin,-180.0f,-180.0f,-100.0f); + //VectorSet(pcjMax,60.0f,60.0f,45.0f); + VectorSet(pcjMax,180.0f,180.0f,45.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rhumerus",pcjflags|RAG_BONE_LIGHTWEIGHT,4.0f,pcjMin,pcjMax,500); + //VectorSet(pcjMin,-45.0f,-60.0f,-45.0f); + VectorSet(pcjMin,-180.0f,-180.0f,-100.0f); + //VectorSet(pcjMax,60.0f,90.0f,100.0f); + VectorSet(pcjMax,180.0f,180.0f,100.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lhumerus",pcjflags|RAG_BONE_LIGHTWEIGHT,4.0f,pcjMin,pcjMax,500); + + //-120/120 + VectorSet(pcjMin,-120.0f,-20.0f,-20.0f); + VectorSet(pcjMax,50.0f,20.0f,-20.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rradius",pcjflags|RAG_BONE_LIGHTWEIGHT,3.0f,pcjMin,pcjMax,500); + VectorSet(pcjMin,-120.0f,-20.0f,-20.0f); + VectorSet(pcjMax,5.0f,20.0f,-20.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lradius",pcjflags|RAG_BONE_LIGHTWEIGHT,3.0f,pcjMin,pcjMax,500); + + VectorSet(pcjMin,-90.0f,-50.0f,-20.0f); + VectorSet(pcjMax,50.0f,20.0f,20.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rfemurYZ",pcjflags|RAG_BONE_LIGHTWEIGHT,6.0f,pcjMin,pcjMax,500); + VectorSet(pcjMin,-90.0f,-20.0f,-20.0f); + VectorSet(pcjMax,50.0f,50.0f,20.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"lfemurYZ",pcjflags|RAG_BONE_LIGHTWEIGHT,6.0f,pcjMin,pcjMax,500); + + //120 + VectorSet(pcjMin,-20.0f,-15.0f,-15.0f); + VectorSet(pcjMax,120.0f,15.0f,15.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"rtibia",pcjflags|RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,4.0f,pcjMin,pcjMax,500); + VectorSet(pcjMin,20.0f,-15.0f,-15.0f); + VectorSet(pcjMax,120.0f,15.0f,15.0f); + G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ltibia",pcjflags|RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,4.0f,pcjMin,pcjMax,500); +#endif + + + 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,"rtibia",RAG_EFFECTOR|RAG_BONE_LIGHTWEIGHT,(4.0f*sRadELeg)*fRadScale); +// G2_Set_Bone_Angles_Rag(ghoul2, mod_a,blist,"ltibia",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); +//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); +#if 0 + if (rhand>0) + { + boneInfo_t &bone=blist[rhand]; + SRagEffector &e=ragEffectors[bone.ragIndex]; + VectorCopy(bone.originalOrigin,handPos); + VectorCopy(e.currentOrigin,handPos2); + } +#endif + int k; + + CRagDollUpdateParams 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; + + //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, or if this bone is still in solid + 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; + 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()=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; i=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*=2; //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); + } +} + +#ifdef _DEBUG +#define _DEBUG_BONE_NAMES +#endif + +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 + } + } +} + +#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 +#ifndef DEDICATED + if (cgvm) + { + ragCallbackTraceLine_t *callData = (ragCallbackTraceLine_t *)cl.mSharedMemory; + + VectorCopy(start, callData->start); + VectorCopy(end, callData->end); + VectorCopy(mins, callData->mins); + VectorCopy(maxs, callData->maxs); + callData->ignore = passEntityNum; + callData->mask = contentmask; + + VM_Call(cgvm, CG_RAG_CALLBACK, RAG_CALLBACK_TRACELINE); + + *results = callData->tr; + } + else +#endif // !DEDICATED + { + results->entityNum = ENTITYNUM_NONE; + //SV_Trace(results, start, mins, maxs, end, passEntityNum, contentmask, eG2TraceType, useLod); + CM_BoxTrace(results, start, end, mins, maxs, 0, contentmask, 0); + results->entityNum = results->fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + } + +#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 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; + 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 (gravity) + { //factor it in before we do anything. + VectorCopy(e.currentOrigin, ground); + ground[2] -= 1.0f; + + Rag_Trace(&tr, e.currentOrigin, 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(e.currentOrigin, 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(e.currentOrigin, goalSpot); + return true; + } + + //get the projected origin based on velocity. + VectorMA(e.currentOrigin, 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(e.currentOrigin, goalSpot); + return true; + } + + Rag_Trace(&tr, e.currentOrigin, 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) +{ +#ifdef DEDICATED + return; +#else + if (!cgvm) + { + return; + } + + ragCallbackDebugBox_t *callData = (ragCallbackDebugBox_t *)cl.mSharedMemory; + + callData->duration = duration; + VectorCopy(mins, callData->mins); + VectorCopy(maxs, callData->maxs); + + VM_Call(cgvm, CG_RAG_CALLBACK, RAG_CALLBACK_DEBUGBOX); +#endif +} + +static inline void G2_RagDebugLine(vec3_t start, vec3_t end, int time, int color, int radius) +{ +#ifdef DEDICATED + return; +#else + if (!cgvm) + { + return; + } + + ragCallbackDebugLine_t *callData = (ragCallbackDebugLine_t *)cl.mSharedMemory; + + VectorCopy(start, callData->start); + VectorCopy(end, callData->end); + callData->time = time; + callData->color = color; + callData->radius = radius; + + VM_Call(cgvm, CG_RAG_CALLBACK, RAG_CALLBACK_DEBUGLINE); +#endif // !DEDICATED +} +#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;i 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); +#ifndef DEDICATED + 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); + } +#endif + } + } + 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); +#ifndef DEDICATED + 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); + } +#endif + } + } + } + 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); +#ifndef DEDICATED + 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); + } +#endif + } + } + 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); +#ifndef DEDICATED + 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); + } +#endif + } + } + } + } + } + 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 + +#ifdef _RAG_PRINT_TEST +void G2_RagPrintMatrix(mdxaBone_t *mat) +{ + char x[1024]; + x[0] = 0; + int n = 0; + while (n < 3) + { + int o = 0; + while (o < 4) + { + strcat(x, va("%f ", mat->matrix[n][o])); + o++; + } + n++; + } + strcat(x, "\n"); + Com_Printf(x); +} +#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 +void G2_GetRagBoneMatrixLow(CGhoul2Info &ghoul2, int boneNum, mdxaBone_t &retMatrix); +void G2_GetBoltMatrixLow(CGhoul2Info &ghoul2,int boltNum,const vec3_t scale,mdxaBone_t &retMatrix); +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.animModel, 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); + +#if 0 + //We have the anim matrix pelvis pos now, so get the normal one as well + G2_GetBoneMatrixLow(ghoul2, boneIndex, params->scale, final, unused1, unused2); + G2API_GiveMeVectorFromMatrix(&final, ORIGIN, pos); + G2API_GiveMeVectorFromMatrix(&final, POSITIVE_X, dir); +#else + //We have the anim matrix pelvis pos now, so get the normal one as well + //G2_GetRagBoneMatrixLow(ghoul2, boneIndex, x); + int bolt = G2_Add_Bolt(&ghoul2, ghoul2.mBltlist, ghoul2.mSlist, "pelvis"); + G2_GetBoltMatrixLow(ghoul2, bolt, params->scale, x); + Multiply_3x4Matrix(&final, &worldMatrix, &x); + G2API_GiveMeVectorFromMatrix(&final, ORIGIN, pos); + G2API_GiveMeVectorFromMatrix(&final, POSITIVE_X, dir); +#endif + + /* + //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 upelvisPos, uanimPelvisPos; + vec3_t blah; + VectorCopy(pelvisPos, upelvisPos); + VectorCopy(animPelvisPos, uanimPelvisPos); + upelvisPos[2] += 64; + uanimPelvisPos[2] += 64; + + VectorMA(upelvisPos, 32.0f, pelvisDir, blah); + G2_RagDebugLine(upelvisPos, blah, 50, 0x00ff00, 1); + VectorMA(uanimPelvisPos, 32.0f, animPelvisDir, blah); + G2_RagDebugLine(uanimPelvisPos, blah, 50, 0xff0000, 1); + */ + + //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].animModel, 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; + + if (d > 16.0f || + d < -16.0f) + { //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) + { + G2_RagDebugLine(basePos, pu, 50, 0xff00ff, 1); + } + else if (skel->name[0] == 'l') + { + G2_RagDebugLine(basePos, pu, 50, 0xffff00, 1); + } + else if (skel->name[0] == 'r') + { + G2_RagDebugLine(basePos, pu, 50, 0xffffff, 1); + } + else + { + 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 //just use the spot directly + 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, 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 0 +#ifndef DEDICATED + if (cgvm && bone.solidCount > 8) + { //make a callback and see if the cgame wants to help us out + 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]) + { + 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); + } + } +#endif +#endif + +#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 + + //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) +{ +#ifdef DEDICATED + return; +#else + 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); +#endif +} + +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;i50.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; + } + } + + //bone.solidCount = allSolidCount; + 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 < 32 || (bone.RagFlags & RAG_UNSNAPPABLE) )) //32 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(mod_a, 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; + + if (bone.startFrame != startFrame || bone.endFrame != endFrame || params->forceAnimOnBone) + { //if it's already on this anim leave it alone, to allow smooth transitions into IK on the current anim if it is so desired. + 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(); +} + +void G2_RemoveRedundantBoneOverrides(boneInfo_v &blist, int *activeBones) +{ + int i; + + // walk the surface list, removing surface overrides or generated surfaces that are pointing at surfaces that aren't active anymore + for (i=0; imFileName)); + model_t *mod_a = R_GetModelByHandle(mod_m->mdxm->animIndex); + + return (G2_Find_Bone(mod_a, ghoul2->mBlist, boneName)); +} \ No newline at end of file diff --git a/codemp/ghoul2/G2_gore.h b/codemp/ghoul2/G2_gore.h new file mode 100644 index 0000000..7d52892 --- /dev/null +++ b/codemp/ghoul2/G2_gore.h @@ -0,0 +1,201 @@ +#if defined (_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif +#if !defined(G2_GORE_H_INC) +#define G2_GORE_H_INC + +#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 + +/// ragdoll stuff + +#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; + + //at some point I'll want to make VM callbacks in here. For now I am just doing nothing. + 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(const vec3_t p1,const vec3_t p2,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; + + //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 + + qboolean 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 + +}; +#endif //G2_GORE_H_INC +//rww - RAGDOLL_END \ No newline at end of file diff --git a/codemp/ghoul2/G2_local.h b/codemp/ghoul2/G2_local.h new file mode 100644 index 0000000..949b468 --- /dev/null +++ b/codemp/ghoul2/G2_local.h @@ -0,0 +1,223 @@ +// defines to setup the + +#include "ghoul2_shared.h" + + +//rww - RAGDOLL_BEGIN +class CRagDollUpdateParams; +//rww - RAGDOLL_END + +class CMiniHeap; + +#define GHOUL2_CRAZY_SMOOTH 0x2000 //hack for smoothing during ugly situations. forgive me. + + +// internal surface calls G2_surfaces.cpp +qboolean G2_SetSurfaceOnOff (CGhoul2Info *ghlInfo, surfaceInfo_v &slist, const char *surfaceName, const int offFlags); +int G2_IsSurfaceOff (CGhoul2Info *ghlInfo, surfaceInfo_v &slist, const char *surfaceName); +qboolean G2_SetRootSurface( CGhoul2Info_v &ghoul2, const int modelIndex, const char *surfaceName); +int G2_AddSurface(CGhoul2Info *ghoul2, int surfaceNumber, int polyNumber, float BarycentricI, float BarycentricJ, int lod ); +qboolean G2_RemoveSurface(surfaceInfo_v &slist, const int index); +surfaceInfo_t *G2_FindOverrideSurface(int surfaceNum, surfaceInfo_v &surfaceList); +int G2_IsSurfaceLegal(void *mod, const char *surfaceName, int *flags); +int G2_GetParentSurface(CGhoul2Info *ghlInfo, const int index); +int G2_GetSurfaceIndex(CGhoul2Info *ghlInfo, const char *surfaceName); +int G2_IsSurfaceRendered(CGhoul2Info *ghlInfo, const char *surfaceName, surfaceInfo_v &slist); + +// internal bone calls - G2_Bones.cpp +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, qhandle_t *modelList, + const int modelIndex, const int blendTime, const int currentTime); +qboolean G2_Remove_Bone (CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName); +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); +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, qhandle_t *modelList, int modelIndex); +qboolean G2_Get_Bone_Anim_Range(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName, int *startFrame, int *endFrame); +qboolean G2_Pause_Bone_Anim(CGhoul2Info *ghlInfo, boneInfo_v &blist, const char *boneName, const int currentTime ); +qboolean G2_IsPaused(const char *fileName, boneInfo_v &blist, const char *boneName); +qboolean G2_Stop_Bone_Anim(const char *fileName, boneInfo_v &blist, const char *boneName); +qboolean G2_Stop_Bone_Angles(const char *fileName, boneInfo_v &blist, const char *boneName); +//rww - RAGDOLL_BEGIN +void G2_Animate_Bone_List(CGhoul2Info_v &ghoul2, const int currentTime, const int index,CRagDollUpdateParams *params); +//rww - RAGDOLL_END +void G2_Init_Bone_List(boneInfo_v &blist); +int G2_Find_Bone_In_List(boneInfo_v &blist, const int boneNum); +void G2_RemoveRedundantBoneOverrides(boneInfo_v &blist, int *activeBones); +qboolean G2_Set_Bone_Angles_Matrix(const char *fileName, boneInfo_v &blist, const char *boneName, const mdxaBone_t &matrix, + const int flags, qhandle_t *modelList, const int modelIndex, const int blendTime, const int currentTime); +int G2_Get_Bone_Index(CGhoul2Info *ghoul2, const char *boneName); +qboolean G2_Set_Bone_Angles_Index(boneInfo_v &blist, const int index, + const float *angles, const int flags, const Eorientations yaw, + const Eorientations pitch, const Eorientations roll, qhandle_t *modelList, + const int modelIndex, const int blendTime, const int currentTime); +qboolean G2_Set_Bone_Angles_Matrix_Index(boneInfo_v &blist, const int index, + const mdxaBone_t &matrix, const int flags, qhandle_t *modelList, + const int modelIndex, const int blendTime, const int currentTime); +qboolean G2_Stop_Bone_Anim_Index(boneInfo_v &blist, const int index); +qboolean G2_Stop_Bone_Angles_Index(boneInfo_v &blist, const int index); +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 blendTime, const int numFrames); +qboolean G2_Get_Bone_Anim_Index( boneInfo_v &blist, const int index, const int currentTime, + float *currentFrame, int *startFrame, int *endFrame, int *flags, float *retAnimSpeed, qhandle_t *modelList, int modelIndex); + +// misc functions G2_misc.cpp +void G2_List_Model_Surfaces(const char *fileName); +void G2_List_Model_Bones(const char *fileName, int frame); +qboolean G2_GetAnimFileName(const char *fileName, char **filename); +#ifdef _G2_GORE +void G2_TraceModels(CGhoul2Info_v &ghoul2, vec3_t rayStart, vec3_t rayEnd, CollisionRecord_t *collRecMap, int entNum, int traceFlags, 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, CollisionRecord_t *collRecMap, int entNum, int traceFlags, int useLod, float fRadius); +#endif +void TransformAndTranslatePoint (const vec3_t in, vec3_t out, mdxaBone_t *mat); +#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 +void G2_GenerateWorldMatrix(const vec3_t angles, const vec3_t origin); +void TransformPoint (const vec3_t in, vec3_t out, mdxaBone_t *mat); +void Inverse_Matrix(mdxaBone_t *src, mdxaBone_t *dest); +void *G2_FindSurface(void *mod, int index, int lod); +qboolean G2_SaveGhoul2Models(CGhoul2Info_v &ghoul2, char **buffer, int *size); +void G2_LoadGhoul2Model(CGhoul2Info_v &ghoul2, char *buffer); + +// internal bolt calls. G2_bolts.cpp +int G2_Add_Bolt(CGhoul2Info *ghlInfo, boltInfo_v &bltlist, surfaceInfo_v &slist, const char *boneName); +qboolean G2_Remove_Bolt (boltInfo_v &bltlist, int index); +void G2_Init_Bolt_List(boltInfo_v &bltlist); +int G2_Find_Bolt_Bone_Num(boltInfo_v &bltlist, const int boneNum); +int G2_Find_Bolt_Surface_Num(boltInfo_v &bltlist, const int surfaceNum, const int flags); +int G2_Add_Bolt_Surf_Num(CGhoul2Info *ghlInfo, boltInfo_v &bltlist, surfaceInfo_v &slist, const int surfNum); +void G2_RemoveRedundantBolts(boltInfo_v &bltlist, surfaceInfo_v &slist, int *activeSurfaces, int *activeBones); + + +// API calls - G2_API.cpp +void G2API_SetTime(int currentTime, int clock); +int G2API_GetTime(int argTime); + +qhandle_t G2API_PrecacheGhoul2Model(const char *fileName); + +int G2API_InitGhoul2Model(CGhoul2Info_v **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin = NULL, + qhandle_t customShader = NULL, int modelFlags = 0, int lodBias = 0); +qboolean G2API_SetLodBias(CGhoul2Info *ghlInfo, int lodBias); +qboolean G2API_SetSkin(CGhoul2Info *ghlInfo, qhandle_t customSkin, qhandle_t renderSkin); +qboolean G2API_SetShader(CGhoul2Info *ghlInfo, qhandle_t customShader); +qboolean G2API_HasGhoul2ModelOnIndex(CGhoul2Info_v **ghlRemove, const int modelIndex); +qboolean G2API_RemoveGhoul2Model(CGhoul2Info_v **ghlRemove, const int modelIndex); +qboolean G2API_RemoveGhoul2Models(CGhoul2Info_v **ghlRemove); +qboolean G2API_SetSurfaceOnOff(CGhoul2Info_v &ghoul2, const char *surfaceName, const int flags); +int G2API_GetSurfaceOnOff(CGhoul2Info *ghlInfo, const char *surfaceName); +qboolean G2API_SetRootSurface(CGhoul2Info_v &ghoul2, 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_SetBoneAnim(CGhoul2Info_v &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 = -1, const int blendTime = -1); +qboolean G2API_GetBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, qhandle_t *modelList); +qboolean G2API_GetAnimRange(CGhoul2Info *ghlInfo, const char *boneName, int *startFrame, int *endFrame); +qboolean G2API_PauseBoneAnim(CGhoul2Info *ghlInfo, const char *boneName, const int currentTime); +qboolean G2API_IsPaused(CGhoul2Info *ghlInfo, const char *boneName); +qboolean G2API_StopBoneAnim(CGhoul2Info *ghlInfo, const char *boneName); + + +qboolean G2API_SetBoneAngles(CGhoul2Info_v &ghoul2, const int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const Eorientations up, const Eorientations left, const Eorientations forward, + qhandle_t *modelList, int blendTime, int currentTime ); + +qboolean G2API_StopBoneAngles(CGhoul2Info *ghlInfo, const char *boneName); +qboolean G2API_RemoveBone(CGhoul2Info *ghlInfo, const char *boneName); +void G2API_AnimateG2Models(CGhoul2Info_v &ghoul2, float speedVar); +qboolean G2API_RemoveBolt(CGhoul2Info *ghlInfo, const int index); +int G2API_AddBolt(CGhoul2Info_v &ghoul2, const int modelIndex, const char *boneName); +int G2API_AddBoltSurfNum(CGhoul2Info *ghlInfo, const int surfIndex); +void G2API_SetBoltInfo(CGhoul2Info_v &ghoul2, int modelIndex, int boltInfo); +qboolean G2API_AttachG2Model(CGhoul2Info_v &ghoul2From, int modelFrom, CGhoul2Info_v &ghoul2To, 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, vec3_t scale); + +void G2API_ListSurfaces(CGhoul2Info *ghlInfo); +void G2API_ListBones(CGhoul2Info *ghlInfo, int frame); +qboolean G2API_HaveWeGhoul2Models(CGhoul2Info_v &ghoul2); +void G2API_SetGhoul2ModelIndexes(CGhoul2Info_v &ghoul2, qhandle_t *modelList, qhandle_t *skinList); +qboolean G2API_SetGhoul2ModelFlags(CGhoul2Info *ghlInfo, const int flags); +int G2API_GetGhoul2ModelFlags(CGhoul2Info *ghlInfo); + +qboolean G2API_GetAnimFileName(CGhoul2Info *ghlInfo, char **filename); +void G2API_CollisionDetect(CollisionRecord_t *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, int traceFlags, int useLod, float fRadius); + +void G2API_GiveMeVectorFromMatrix(mdxaBone_t *boltMatrix, Eorientations flags, vec3_t vec); +int G2API_CopyGhoul2Instance(CGhoul2Info_v &g2From, CGhoul2Info_v &g2To, int modelIndex); +void G2API_CleanGhoul2Models(CGhoul2Info_v **ghoul2Ptr); +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_v &ghoul2, int modelIndex); +qboolean G2API_SetBoneAnglesMatrix(CGhoul2Info *ghlInfo, const char *boneName, const mdxaBone_t &matrix, const int flags, + qhandle_t *modelList, int blendTime = 0, int currentTime = 0); +qboolean G2API_SetNewOrigin(CGhoul2Info_v &ghoul2, const int boltIndex); +int G2API_GetBoneIndex(CGhoul2Info *ghlInfo, const char *boneName); +qboolean G2API_StopBoneAnglesIndex(CGhoul2Info *ghlInfo, const int index); +qboolean G2API_StopBoneAnimIndex(CGhoul2Info *ghlInfo, const int index); +qboolean G2API_SetBoneAnglesIndex(CGhoul2Info *ghlInfo, const int index, const vec3_t angles, const int flags, + const int yaw, const int pitch, const int roll, + qhandle_t *modelList, int blendTime, int currentTime); +qboolean G2API_SetBoneAnglesMatrixIndex(CGhoul2Info *ghlInfo, const int index, const mdxaBone_t &matrix, + const int flags, qhandle_t *modelList, int blendTime, int currentTime); +qboolean G2API_DoesBoneExist(CGhoul2Info *ghlInfo, const char *boneName); +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); +qboolean G2API_SaveGhoul2Models(CGhoul2Info_v &ghoul2, char **buffer, int *size); +void G2API_LoadGhoul2Models(CGhoul2Info_v &ghoul2, char *buffer); +void G2API_LoadSaveCodeDestructGhoul2Info(CGhoul2Info_v &ghoul2); +void G2API_FreeSaveBuffer(char *buffer); +char *G2API_GetAnimFileNameIndex(qhandle_t modelIndex); +int G2API_GetSurfaceRenderStatus(CGhoul2Info *ghlInfo, const char *surfaceName); +void G2API_CopySpecificG2Model(CGhoul2Info_v &ghoul2From, int modelFrom, CGhoul2Info_v &ghoul2To, int modelTo); +void G2API_DuplicateGhoul2Instance(CGhoul2Info_v &g2From, CGhoul2Info_v **g2To); +void G2API_SetBoltInfo(CGhoul2Info_v &ghoul2, int modelIndex, int boltInfo); + +class CRagDollUpdateParams; +class CRagDollParams; + +void G2API_AbsurdSmoothing(CGhoul2Info_v &ghoul2, qboolean status); + +void G2API_SetRagDoll(CGhoul2Info_v &ghoul2,CRagDollParams *parms); +void G2API_ResetRagDoll(CGhoul2Info_v &ghoul2); +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); + +void G2API_AttachInstanceToEntNum(CGhoul2Info_v &ghoul2, int entityNum, qboolean server); +void G2API_ClearAttachedInstance(int entityNum); +void G2API_CleanEntAttachments(void); +qboolean G2API_OverrideServerWithClientData(CGhoul2Info *serverInstance); + +extern qboolean gG2_GBMNoReconstruct; +extern qboolean gG2_GBMUseSPMethod; +// From tr_ghoul2.cpp +void G2_ConstructGhoulSkeleton( CGhoul2Info_v &ghoul2,const int frameNum,bool checkForNewOrigin,const vec3_t scale); + +qboolean G2API_SkinlessModel(CGhoul2Info *g2); + +#ifdef _G2_GORE +int G2API_GetNumGoreMarks(CGhoul2Info *g2); +void G2API_AddSkinGore(CGhoul2Info_v &ghoul2,SSkinGoreData &gore); +void G2API_ClearSkinGore ( CGhoul2Info_v &ghoul2 ); +#endif // _SOF2 + +int G2API_Ghoul2Size ( CGhoul2Info_v &ghoul2 ); + diff --git a/codemp/ghoul2/G2_misc.cpp b/codemp/ghoul2/G2_misc.cpp new file mode 100644 index 0000000..032e2d3 --- /dev/null +++ b/codemp/ghoul2/G2_misc.cpp @@ -0,0 +1,1926 @@ +// leave this as first line for PCH reasons... +// + + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + + +#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 + +#include "../server/server.h" +#include "G2_local.h" + +#ifdef _G2_GORE +#include "G2_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; +} + +#ifdef _DEBUG +int g_goreAllocs = 0; +int g_goreTexAllocs = 0; +#endif + +CGoreSet *NewGoreSet() +{ + CGoreSet *ret=new CGoreSet(CurrentGoreSet++); +#ifdef _DEBUG + g_goreAllocs++; +#endif + 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 ) + { +#ifdef _DEBUG + g_goreAllocs--; +#endif + 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 // _SOF2 + +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; + model_t *currentModel; + int lod; + vec3_t rayStart; + vec3_t rayEnd; + CollisionRecord_t *collRecMap; + int entNum; + int modelIndex; + skin_t *skin; + shader_t *cust_shader; + int *TransformedVertsArray; + int traceFlags; + 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, + model_t *initcurrentModel, + int initlod, + vec3_t initrayStart, + vec3_t initrayEnd, + CollisionRecord_t *initcollRecMap, + int initentNum, + int initmodelIndex, + skin_t *initskin, + shader_t *initcust_shader, + int *initTransformedVertsArray, + int inittraceFlags, +#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), + traceFlags(inittraceFlags), + 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); + hitOne = false; + } + +}; + +// 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) + { + 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 // _XBOX + +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((void*)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); +#ifdef _G2_GORE + } +#endif + + // 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 +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 (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 +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; + + 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); + + float *verts = (float *)TS.TransformedVertsArray[surface->thisSurfaceIndex]; + int numVerts = surface->numVerts; + int flags=15; + 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; + } + 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++; + if (!TS.gore) + { + return; + } + 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_GORE, qtrue ); + +#ifdef _DEBUG + g_goreTexAllocs++; +#endif + + if ( gore->tex[TS.lod] ) + { + Z_Free(gore->tex[TS.lod]); +#ifdef _DEBUG + g_goreTexAllocs--; +#endif + } + + 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? + int i; + 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 (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); + + // 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.traceFlags == 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 + //It happens. And the assert is bugging me. + 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.traceFlags == 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]; + dist = side/side2; + VectorMA(TS.rayStart, -dist, distVect, hitPoint); +#endif + + VectorSubtract(hitPoint, TS.rayStart, distVect); + newCol.mDistance = VectorLength(distVect); + + // 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 (!(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.traceFlags == G2_RETURNONHIT) + ) + { + TS.hitOne = true; + return; + } + } + else + { + // go away and trace the polys in this surface + if (G2_TracePolys(surface, surfInfo, TS) + && (TS.traceFlags == 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, CollisionRecord_t *collRecMap, int entNum, int 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, CollisionRecord_t *collRecMap, int entNum, int 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 && ghoul2[i].mSkin < tr.numSkins ) + { + skin = R_GetSkinByHandle( ghoul2[i].mSkin ); + } + else + { + skin = NULL; + } + +#ifdef _G2_GORE + if (collRecMap) + { + lod = G2_DecideTraceLod(ghoul2[i],useLod); + } + else + { + lod=useLod; + assert(ghoul2[i].currentModel); + if (lod>=ghoul2[i].currentModel->numLods) + { + return; + } + } +#else + lod = G2_DecideTraceLod(ghoul2[i],useLod); +#endif + + //reset the quick surface override lookup + G2_FindOverrideSurface(-1, ghoul2[i].mSlist); + +#ifdef _G2_GORE + CTraceSurface TS(ghoul2[i].mSurfaceRoot, ghoul2[i].mSlist, (model_t *)ghoul2[i].currentModel, lod, rayStart, rayEnd, collRecMap, entNum, i, skin, cust_shader, ghoul2[i].mTransformedVertsArray, eG2TraceType, fRadius, ssize, tsize, theta, shader, &ghoul2[i], gore); +#else + CTraceSurface TS(ghoul2[i].mSurfaceRoot, ghoul2[i].mSlist, (model_t *)ghoul2[i].currentModel, lod, rayStart, rayEnd, collRecMap, entNum, i, skin, cust_shader, ghoul2[i].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(void *mod_t, int index, int lod) +{ + // damn include file dependancies + model_t *mod = (model_t *)mod_t; + + // point at first lod list + byte *current = (byte*)((int)mod->mdxm + (int)mod->mdxm->ofsLODs); + int i; + + //walk the lods + 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 + current += indexes->offsets[index]; + + return (void *)current; +} + +#define SURFACE_SAVE_BLOCK_SIZE sizeof(surfaceInfo_t) +#define BOLT_SAVE_BLOCK_SIZE (sizeof(boltInfo_t) - sizeof(mdxaBone_t)) +#define BONE_SAVE_BLOCK_SIZE sizeof(boneInfo_t) + +qboolean G2_SaveGhoul2Models(CGhoul2Info_v &ghoul2, char **buffer, int *size) +{ + + // is there anything to save? + if (!ghoul2.size()) + { + *buffer = (char *)Z_Malloc(4, TAG_GHOUL2, qtrue); + int *tempBuffer = (int *)*buffer; + *tempBuffer = 0; + *size = 4; + return qtrue; + } + + // yeah, lets get busy + *size = 0; + + // this one isn't a define since I couldn't work out how to figure it out at compile time + int ghoul2BlockSize = (int)&ghoul2[0].mTransformedVertsArray - (int)&ghoul2[0].mModelindex; + + // add in count for number of ghoul2 models + *size += 4; + // start out working out the total size of the buffer we need to allocate + int i; // Linux GCC is forcing new scoping rules + for (i=0; i i) && + (nextGhoul2[i].mModelindex != -1) && + (nextGhoul2[i].mBlist.size() > x) && + (nextGhoul2[i].mBlist[x].boneNumber != -1)) + { + boneInfo_t &nextBone = nextGhoul2[i].mBlist[x]; + // does this bone override actually have anything in it, and if it does, is it a bone angles override? + if ((bone.boneNumber != -1) && ((bone.flags) & (BONE_ANGLES_TOTAL))) + { + float *nowMatrix = (float*) &bone.matrix; + float *nextMatrix = (float*) &nextBone.matrix; + float *newMatrix = (float*) &bone.newMatrix; + // now interpolate the matrix + for (int z=0; z < 12; z++) + { + newMatrix[z] = nowMatrix[z] + interpolation * ( nextMatrix[z] - nowMatrix[z] ); + } + } + } + else + { + memcpy(&ghoul2[i].mBlist[x].newMatrix, &ghoul2[i].mBlist[x].matrix, sizeof(mdxaBone_t)); + } + } + } + } +} diff --git a/codemp/ghoul2/G2_surfaces.cpp b/codemp/ghoul2/G2_surfaces.cpp new file mode 100644 index 0000000..16dca4e --- /dev/null +++ b/codemp/ghoul2/G2_surfaces.cpp @@ -0,0 +1,677 @@ +// leave this as first line for PCH reasons... +// + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + + +#if !defined(TR_LOCAL_H) + #include "../renderer/tr_local.h" +#endif + +#if !defined(G2_H_INC) + #include "G2.h" +#endif + #include "G2_local.h" +#pragma warning(disable : 4512) //assignment op could not be genereated + +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) { } +}; + +extern void G2_ConstructUsedBoneList(CConstructBoneList &CBL); + + +//===================================================================================================================== +// Surface List handling routines - so entities can determine what surfaces attached to a model are operational or not. + +// find a particular surface in the surface override list +surfaceInfo_t *G2_FindOverrideSurface(int surfaceNum, surfaceInfo_v &surfaceList) +{ + int i; + + // look through entire list + for(i=0; imdxm + 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 + * + ************************************************************************************************/ +mdxmSurface_t *G2_FindSurface(CGhoul2Info *ghlInfo, surfaceInfo_v &slist, const char *surfaceName, + int *surfIndex/*NULL*/) +{ + int i = 0; + // find the model we want + model_t *mod = (model_t *)ghlInfo->currentModel; + mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)mod->mdxm + sizeof(mdxmHeader_t)); + mdxmSurfHierarchy_t *surfInfo; + + // did we find a ghoul 2 model or not? + if (!mod->mdxm) + { + assert(0); + if (surfIndex) + { + *surfIndex = -1; + } + return 0; + } + + // 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)) + { + mdxmSurface_t *surf = (mdxmSurface_t *)G2_FindSurface((void *)mod, slist[i].surface, 0); + // back track and get the surfinfo struct for this surface + 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, surfaceInfo_v &slist, const char *surfaceName, const int offFlags) +{ + int surfIndex = -1; + surfaceInfo_t temp_slist_entry; + mdxmSurface_t *surf; + // find the model we want + model_t *mod = (model_t *)ghlInfo->currentModel; + + // did we find a ghoul 2 model or not? + if (!mod->mdxm) + { + assert(0); + return qfalse; + } + + // first find if we already have this surface in the list + surf = G2_FindSurface(ghlInfo, slist, 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 + slist[surfIndex].offFlags &= ~(G2SURFACEFLAG_OFF | G2SURFACEFLAG_NODESCENDANTS); + slist[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((void*)mod, 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; + + slist.push_back(temp_slist_entry); + } + return qtrue; + } + } + return qfalse; +} + +void G2_SetSurfaceOnOffFromSkin (CGhoul2Info *ghlInfo, qhandle_t renderSkin) +{ + int j; + const skin_t *skin = R_GetSkinByHandle( renderSkin ); + + ghlInfo->mSlist.clear(); //remove any overrides we had before. + ghlInfo->mMeshFrameNum = 0; + + for ( j = 0 ; j < skin->numSurfaces ; j++ ) + { + // the names have both been lowercased + if ( !strcmp( skin->surfaces[j]->shader->name , "*off") ) + { + G2_SetSurfaceOnOff(ghlInfo, ghlInfo->mSlist, skin->surfaces[j]->name, G2SURFACEFLAG_OFF); + } + else + { + int flags; + int surfaceNum = G2_IsSurfaceLegal((void *)ghlInfo->currentModel, skin->surfaces[j]->name, &flags); + if ( (surfaceNum != -1) && (!(flags&G2SURFACEFLAG_OFF)) ) //only turn on if it's not an "_off" surface + { + G2_SetSurfaceOnOff(ghlInfo, ghlInfo->mSlist, skin->surfaces[j]->name, 0); + } + } + } +} + +// return a named surfaces off flags - should tell you if this surface is on or off. +int G2_IsSurfaceOff (CGhoul2Info *ghlInfo, surfaceInfo_v &slist, const char *surfaceName) +{ + model_t *mod = (model_t *)ghlInfo->currentModel; + int surfIndex = -1; + mdxmSurface_t *surf = 0; + + // did we find a ghoul 2 model or not? + if (!mod->mdxm) + { + return 0; + } + + // first find if we already have this surface in the list + surf = G2_FindSurface(ghlInfo, slist, surfaceName, &surfIndex); + if (surf) + { + // set descendants value + return slist[surfIndex].offFlags; + } + // ok, we didn't find it in the surface list. Lets look at the original surface then. + + mdxmSurfHierarchy_t *surface = (mdxmSurfHierarchy_t *) ( (byte *)mod->mdxm + mod->mdxm->ofsSurfHierarchy ); + + for ( int i = 0 ; i < mod->mdxm->numSurfaces ; i++) + { + if (!stricmp(surfaceName, surface->name)) + { + return surface->flags; + } + // find the next surface + surface = (mdxmSurfHierarchy_t *)( (byte *)surface + (int)( &((mdxmSurfHierarchy_t *)0)->childIndexes[ surface->numChildren ] )); + } + + assert(0); + return 0; +} + +void G2_FindRecursiveSurface(model_t *currentModel, int surfaceNum, surfaceInfo_v &rootList, int *activeSurfaces) +{ + int i; + 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, 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); + } + +} + +void G2_RemoveRedundantGeneratedSurfaces(surfaceInfo_v &slist, int *activeSurfaces) +{ + int i; + + // walk the surface list, removing surface overrides or generated surfaces that are pointing at surfaces that aren't active anymore + for (i=0; imdxm) + { + return qfalse; + } + + // first find if we already have this surface in the list + surf = G2_IsSurfaceLegal(mod_m, surfaceName, &flags); + if (surf != -1) + { + // first see if this ghoul2 model already has this as a root surface + if (ghoul2[modelIndex].mSurfaceRoot == surf) + { + return qtrue; + } + + // set the root surface + ghoul2[modelIndex].mSurfaceRoot = surf; + + // ok, now the tricky bits. + // firstly, generate a list of active / on surfaces below the root point + + // gimme some space to put this list into + activeSurfaces = (int *)Z_Malloc(mod_m->mdxm->numSurfaces * 4, TAG_GHOUL2, qtrue); + memset(activeSurfaces, 0, (mod_m->mdxm->numSurfaces * 4)); + activeBones = (int *)Z_Malloc(mod_a->mdxa->numBones * 4, TAG_GHOUL2, qtrue); + memset(activeBones, 0, (mod_a->mdxa->numBones * 4)); + + G2_FindRecursiveSurface(mod_m, surf, ghoul2[modelIndex].mSlist, activeSurfaces); + + // now generate the used bone list + CConstructBoneList CBL(ghoul2[modelIndex].mSurfaceRoot, + activeBones, + ghoul2[modelIndex].mSlist, + mod_m, + ghoul2[modelIndex].mBlist); + + G2_ConstructUsedBoneList(CBL); + + // now remove all procedural or override surfaces that refer to surfaces that arent on this list + G2_RemoveRedundantGeneratedSurfaces(ghoul2[modelIndex].mSlist, activeSurfaces); + + // now remove all bones that are pointing at bones that aren't active + G2_RemoveRedundantBoneOverrides(ghoul2[modelIndex].mBlist, activeBones); + + // then remove all bolts that point at surfaces or bones that *arent* active. + G2_RemoveRedundantBolts(ghoul2[modelIndex].mBltlist, ghoul2[modelIndex].mSlist, activeSurfaces, activeBones); + + // then remove all models on this ghoul2 instance that use those bolts that are being removed. + for (int i=0; i> MODEL_SHIFT) & MODEL_AND; + int boltNum = (ghoul2[i].mModelBoltLink >> BOLT_SHIFT) & BOLT_AND; + // if either the bolt list is too small, or the bolt we are pointing at references nothing, remove this model + if ((ghoul2[boltMod].mBltlist.size() <= boltNum) || + ((ghoul2[boltMod].mBltlist[boltNum].boneNumber == -1) && + (ghoul2[boltMod].mBltlist[boltNum].surfaceNumber == -1))) + { + CGhoul2Info_v *g2i = &ghoul2; + G2API_RemoveGhoul2Model((CGhoul2Info_v **)&g2i, i); + } + } + } + //No support for this, for now. + + // remember to free what we used + Z_Free(activeSurfaces); + Z_Free(activeBones); + + return (qtrue); + } +/* +//g2r if (entstate->ghoul2) + { + CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)entstate->ghoul2); + model_t *mod_m = R_GetModelByHandle(RE_RegisterModel(ghoul2[modelIndex].mFileName)); + model_t *mod_a = R_GetModelByHandle(mod_m->mdxm->animIndex); + int surf; + int flags; + int *activeSurfaces, *activeBones; + + // did we find a ghoul 2 model or not? + if (!mod_m->mdxm) + { + return qfalse; + } + + // first find if we already have this surface in the list + surf = G2_IsSurfaceLegal(mod_m, surfaceName, &flags); + if (surf != -1) + { + // first see if this ghoul2 model already has this as a root surface + if (ghoul2[modelIndex].mSurfaceRoot == surf) + { + return qtrue; + } + + // set the root surface + ghoul2[modelIndex].mSurfaceRoot = surf; + + // ok, now the tricky bits. + // firstly, generate a list of active / on surfaces below the root point + + // gimme some space to put this list into + activeSurfaces = (int *)Z_Malloc(mod_m->mdxm->numSurfaces * 4, TAG_GHOUL2, qtrue); + memset(activeSurfaces, 0, (mod_m->mdxm->numSurfaces * 4)); + activeBones = (int *)Z_Malloc(mod_a->mdxa->numBones * 4, TAG_GHOUL2, qtrue); + memset(activeBones, 0, (mod_a->mdxa->numBones * 4)); + + G2_FindRecursiveSurface(mod_m, surf, ghoul2[modelIndex].mSlist, activeSurfaces); + + // now generate the used bone list + CConstructBoneList CBL(ghoul2[modelIndex].mSurfaceRoot, + activeBones, + ghoul2[modelIndex].mSlist, + mod_m, + ghoul2[modelIndex].mBlist); + + G2_ConstructUsedBoneList(CBL); + + // now remove all procedural or override surfaces that refer to surfaces that arent on this list + G2_RemoveRedundantGeneratedSurfaces(ghoul2[modelIndex].mSlist, activeSurfaces); + + // now remove all bones that are pointing at bones that aren't active + G2_RemoveRedundantBoneOverrides(ghoul2[modelIndex].mBlist, activeBones); + + // then remove all bolts that point at surfaces or bones that *arent* active. + G2_RemoveRedundantBolts(ghoul2[modelIndex].mBltlist, ghoul2[modelIndex].mSlist, activeSurfaces, activeBones); + + // then remove all models on this ghoul2 instance that use those bolts that are being removed. + for (int i=0; i> MODEL_SHIFT) & MODEL_AND; + int boltNum = (ghoul2[i].mModelBoltLink >> BOLT_SHIFT) & BOLT_AND; + // if either the bolt list is too small, or the bolt we are pointing at references nothing, remove this model + if ((ghoul2[boltMod].mBltlist.size() <= boltNum) || + ((ghoul2[boltMod].mBltlist[boltNum].boneNumber == -1) && + (ghoul2[boltMod].mBltlist[boltNum].surfaceNumber == -1))) + { + G2API_RemoveGhoul2Model(entstate, i); + } + } + } + + // remember to free what we used + Z_Free(activeSurfaces); + Z_Free(activeBones); + + 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 ) +{ + + surfaceInfo_t temp_slist_entry; + + // decide if LOD is legal + lod = G2_DecideTraceLod(*(CGhoul2Info *)(ghoul2), lod); + + // first up, see if we have a free one already set up - look only from the end of the constant surfaces onwards + for (int i=0; imSlist.size(); i++) + { + // is the surface count -1? That would indicate it's free + if (ghoul2->mSlist[i].surface == -1) + { + 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; + } + } + + // ok, didn't find one. Better create one + + temp_slist_entry.offFlags = G2SURFACEFLAG_GENERATED; + temp_slist_entry.surface = 10000; + temp_slist_entry.genBarycentricI = BarycentricI; + temp_slist_entry.genBarycentricJ = BarycentricJ; + temp_slist_entry.genPolySurfaceIndex = ((polyNumber & 0xffff) << 16) | (surfaceNumber & 0xffff); + temp_slist_entry.genLod = lod; + + ghoul2->mSlist.push_back(temp_slist_entry); + + return (ghoul2->mSlist.size() -1 ); +} + +qboolean G2_RemoveSurface(surfaceInfo_v &slist, const int index) +{ + // did we find it? + if (index != -1) + { + // set us to be the 'not active' state + slist[index].surface = -1; + + unsigned int newSize = slist.size(); + // now look through the list from the back and see if there is a block of -1's we can resize off the end of the list + for (int i=slist.size()-1; i>-1; i--) + { + if (slist[i].surface == -1) + { + newSize = i; + } + // once we hit one that isn't a -1, we are done. + else + { + break; + } + } + // do we need to resize? + if (newSize != slist.size()) + { + // yes, so lets do it + slist.resize(newSize); + } + + return qtrue; + } + + assert(0); + + // no + return qfalse; +} + + +int G2_GetParentSurface(CGhoul2Info *ghlInfo, const int index) +{ + model_t *mod = (model_t *)ghlInfo->currentModel; + mdxmSurface_t *surf = 0; + mdxmHierarchyOffsets_t *surfIndexes = (mdxmHierarchyOffsets_t *)((byte *)mod->mdxm + sizeof(mdxmHeader_t)); + mdxmSurfHierarchy_t *surfInfo = 0; + + // walk each surface and see if this index is listed in it's children + surf = (mdxmSurface_t *)G2_FindSurface((void *)mod, index, 0); + surfInfo = (mdxmSurfHierarchy_t *)((byte *)surfIndexes + surfIndexes->offsets[surf->thisSurfaceIndex]); + + return surfInfo->parentIndex; + +} + +int G2_GetSurfaceIndex(CGhoul2Info *ghlInfo, const char *surfaceName) +{ + model_t *mod = (model_t *)ghlInfo->currentModel; + int flags; + + return G2_IsSurfaceLegal(mod, 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((model_t *)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((model_t *)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/codemp/ghoul2/ghoul2_shared.h b/codemp/ghoul2/ghoul2_shared.h new file mode 100644 index 0000000..f193fb2 --- /dev/null +++ b/codemp/ghoul2/ghoul2_shared.h @@ -0,0 +1,472 @@ +#if defined (_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif +#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 +#include +#include +#pragma warning (pop) +using namespace std; +/* +Ghoul2 Insert End +*/ + +#define MDXABONEDEF +#include "../renderer/mdx_format.h" + +struct model_s; + +//rww - RAGDOLL_BEGIN +#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 +//rww - RAGDOLL_END + +//=================================================================== +// +// 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 MDXABONEDEF // used in the mdxformat.h file to stop redefinitions of the bone struct. + +// 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 + int lastTime; // this does not go across the network + 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; + + 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), + lastTime(0), + RagFlags(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 + mdxaBone_t position; // this does not go across the network + boltInfo_t(): + boneNumber(-1), + surfaceNumber(-1), + surfaceType(0), + boltUsed(0) + {} +}; + +#ifdef _SOF2 +typedef enum +{ + PGORE_NONE, + PGORE_ARMOR, + PGORE_BULLETSMALL, + PGORE_BULLETMED, + PGORE_BULLETBIG, + PGORE_HEGRENADE, + PGORE_COUNT +} goreEnum_t; + +struct goreEnumShader_t +{ + goreEnum_t shaderEnum; + char shaderName[MAX_QPATH]; +}; + +struct SSkinGoreData +{ + 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 + +// qhandle_t shader; // handle to shader for gore, this better be rendered after the shader of the underlying surface + // this shader should also have "clamp" mode, not tiled. + goreEnum_t shaderEnum; // enum that'll get switched over to the shader's actual handle +}; +#endif // _SOF2 + +#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 + +class CBoneCache; + +// 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 CGhoul2Info +{ +public: + surfaceInfo_v mSlist; + boltInfo_v mBltlist; + boneInfo_v mBlist; +// save from here + int mModelindex; + 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 + 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; + +#ifdef _G2_LISTEN_SERVER_OPT + int entityNum; +#endif + + 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), + currentAnimModelSize(0), + aHeader(0), +#ifdef _G2_LISTEN_SERVER_OPT + entityNum(ENTITYNUM_NONE), +#endif + 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(); + +class CGhoul2Info_v +{ + IGhoul2InfoArray &InfoArray() const + { + return TheGhoul2InfoArray(); + } + + 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: + int mItem; //dont' be bad and muck with this + CGhoul2Info_v() + { + mItem=0; + } + CGhoul2Info_v(const int item) + { //be VERY carefull with what you pass in here + mItem=item; + } + ~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 operator=(const int otherItem) //assigning one from the VM side item number + { + mItem=otherItem; + } + 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 + + +// calling defines for the trace function +enum EG2_Collision +{ + G2_NOCOLLIDE, + G2_COLLIDE, + G2_RETURNONHIT +}; + + +//==================================================================== + +#endif // GHOUL2_SHARED_H_INC diff --git a/codemp/ghoul2/vssver.scc b/codemp/ghoul2/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..ad42de0845c4c20883f2bd207d90ca3f5f62d179 GIT binary patch literal 176 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZFIbQO5$^Ze+fKuGpZr*W?B_sy+Asep z|IX=t0YLT(Aoi-aGX1VT*N+XzehI|S?W+2U<+l1o0@<&CxOK*{Lh%#sew;w|YY^Xt zeR2B#5I-p(`wb8~a9_Q}S&-)^0c5`g;*clIpN)?)`tbtU?|^uAOU&ErTZF;#0D#yv A*Z=?k literal 0 HcmV?d00001 diff --git a/codemp/goblib/goblib.cpp b/codemp/goblib/goblib.cpp new file mode 100644 index 0000000..11553f7 --- /dev/null +++ b/codemp/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/codemp/goblib/goblib.h b/codemp/goblib/goblib.h new file mode 100644 index 0000000..d975935 --- /dev/null +++ b/codemp/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/codemp/goblib/goblib.vcproj b/codemp/goblib/goblib.vcproj new file mode 100644 index 0000000..84482f6 --- /dev/null +++ b/codemp/goblib/goblib.vcproj @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/goblib/mssccprj.scc b/codemp/goblib/mssccprj.scc new file mode 100644 index 0000000..65afff4 --- /dev/null +++ b/codemp/goblib/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[goblib.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp/goblib", FBIAAAAA diff --git a/codemp/goblib/vssver.scc b/codemp/goblib/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..41e0b8853415ebe3d88d6c92114cd950f348ad01 GIT binary patch literal 80 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiYmeY|?rUX%e09DwxmGfp2(eKq_Tfg+ATzR2Xq Z$L}9M90=k&0r~DCg;zqwF9k9I`2b$D81VoA literal 0 HcmV?d00001 diff --git a/codemp/icarus/BlockStream.cpp b/codemp/icarus/BlockStream.cpp new file mode 100644 index 0000000..a0c9495 --- /dev/null +++ b/codemp/icarus/BlockStream.cpp @@ -0,0 +1,698 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// Interpreted Block Stream Functions +// +// -- jweier + +// this include must remain at the top of every Icarus CPP file +#include "icarus.h" + + +#pragma warning(disable : 4100) //unref formal parm +#pragma warning(disable : 4710) //member not inlined + +#include +#include "blockstream.h" + +/* +=================================================================================================== + + CBlockMember + +=================================================================================================== +*/ + +CBlockMember::CBlockMember( void ) +{ + m_id = -1; + m_size = -1; + m_data = NULL; +} + +CBlockMember::~CBlockMember( void ) +{ + Free(); +} + +/* +------------------------- +Free +------------------------- +*/ + +void CBlockMember::Free( void ) +{ + if ( m_data != NULL ) + { + ICARUS_Free ( m_data ); + m_data = NULL; + + m_id = m_size = -1; + } +} + +/* +------------------------- +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 ) +{ + WriteDataPointer( data, strlen(data)+1 ); +} + +void CBlockMember::SetData( vector_t data ) +{ + WriteDataPointer( data, 3 ); +} + +void CBlockMember::SetData( void *data, int size ) +{ + if ( m_data ) + ICARUS_Free( m_data ); + + m_data = ICARUS_Malloc( size ); + memcpy( m_data, data, size ); + m_size = size; +} + +// Member I/O functions + +/* +------------------------- +ReadMember +------------------------- +*/ + +int CBlockMember::ReadMember( char **stream, long *streamPos ) +{ + m_id = *(int *) (*stream + *streamPos); + *streamPos += sizeof( int ); + + if ( m_id == 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 = ICARUS_Malloc( m_size ); + float infinite = Q3_INFINITE; + memcpy( m_data, &infinite, m_size ); + } + else + { + m_size = *(long *) (*stream + *streamPos); + *streamPos += sizeof( long ); + m_data = ICARUS_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( void ) +{ + CBlockMember *newblock = new CBlockMember; + + if ( newblock == NULL ) + return NULL; + + newblock->SetData( m_data, m_size ); + newblock->SetSize( m_size ); + newblock->SetID( m_id ); + + return newblock; +} + +/* +=================================================================================================== + + CBlock + +=================================================================================================== +*/ + +CBlock::CBlock( void ) +{ + m_flags = 0; + m_id = 0; +} + +CBlock::~CBlock( void ) +{ + Free(); +} + +/* +------------------------- +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( void ) +{ + int numMembers = GetNumMembers(); + CBlockMember *bMember; + + while ( numMembers-- ) + { + bMember = GetMember( numMembers ); + + if (!bMember) + return false; + + delete bMember; + } + + 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 ) +{ + CBlockMember *bMember = new CBlockMember; + + bMember->SetID( member_id ); + + bMember->SetData( member_data ); + bMember->SetSize( strlen(member_data) + 1 ); + + AddMember( bMember ); + + return true; +} + +int CBlock::Write( int member_id, vector_t member_data ) +{ + CBlockMember *bMember; + + bMember = new CBlockMember; + + bMember->SetID( member_id ); + bMember->SetData( member_data ); + bMember->SetSize( sizeof(vector_t) ); + + AddMember( bMember ); + + return true; +} + +int CBlock::Write( int member_id, float member_data ) +{ + CBlockMember *bMember = new CBlockMember; + + bMember->SetID( member_id ); + bMember->WriteData( member_data ); + bMember->SetSize( sizeof(member_data) ); + + AddMember( bMember ); + + return true; +} + +int CBlock::Write( int member_id, int member_data ) +{ + CBlockMember *bMember = new CBlockMember; + + bMember->SetID( member_id ); + bMember->WriteData( member_data ); + bMember->SetSize( sizeof(member_data) ); + + AddMember( bMember ); + + return true; +} + + +int CBlock::Write( CBlockMember *bMember ) +{ +// 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( void ) +{ + 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() ); + } + + return newblock; +} + +/* +=================================================================================================== + + CBlockStream + +=================================================================================================== +*/ + +CBlockStream::CBlockStream( void ) +{ + m_stream = NULL; + m_streamPos = 0; +} + +CBlockStream::~CBlockStream( void ) +{ +} + +/* +------------------------- +GetChar +------------------------- +*/ + +char CBlockStream::GetChar( void ) +{ + char data; + + data = *(char*) (m_stream + m_streamPos); + m_streamPos += sizeof( data ); + + return data; +} + +/* +------------------------- +GetUnsignedInteger +------------------------- +*/ + +unsigned CBlockStream::GetUnsignedInteger( void ) +{ + unsigned data; + + data = *(unsigned *) (m_stream + m_streamPos); + m_streamPos += sizeof( data ); + + return data; +} + +/* +------------------------- +GetInteger +------------------------- +*/ + +int CBlockStream::GetInteger( void ) +{ + int data; + + data = *(int *) (m_stream + m_streamPos); + m_streamPos += sizeof( data ); + + return data; +} + +/* +------------------------- +GetLong +------------------------- +*/ + +long CBlockStream::GetLong( void ) +{ + long data; + + data = *(long *) (m_stream + m_streamPos); + m_streamPos += sizeof( data ); + + return data; +} + +/* +------------------------- +GetFloat +------------------------- +*/ + +float CBlockStream::GetFloat( void ) +{ + float data; + + data = *(float *) (m_stream + m_streamPos); + m_streamPos += sizeof( data ); + + return data; +} + +// Extension stripping utility + +/* +------------------------- +StripExtension +------------------------- +*/ + +void CBlockStream::StripExtension( const char *in, char *out ) +{ + int i = strlen(in); + + while ( (in[i] != '.') && (i >= 0) ) + i--; + + if ( i < 0 ) + { + strcpy(out, in); + return; + } + + strncpy(out, in, i); +} + +/* +------------------------- +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 ) +{ + char newName[MAX_FILENAME_LENGTH], *id_header = IBI_HEADER_ID; + float version = IBI_VERSION; + + //Clear the temp string + memset(newName, 0, sizeof(newName)); + + //Strip the extension and add the BLOCK_EXT extension + strcpy((char *) m_fileName, filename); + StripExtension( (char *) m_fileName, (char *) &newName ); + strcat((char *) newName, IBI_EXT); + + //Recover that as the active filename + strcpy(m_fileName, newName); + + if ( ((m_fileHandle = fopen(m_fileName, "wb")) == NULL) ) + { + return false; + } + + fwrite( id_header, 1, sizeof(id_header), m_fileHandle ); + fwrite( &version, 1, sizeof(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 ) +{ + 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(); + + return true; +} + +/* +------------------------- +BlockAvailable +------------------------- +*/ + +int CBlockStream::BlockAvailable( void ) +{ + if ( m_streamPos >= m_fileSize ) + return false; + + return true; +} + +/* +------------------------- +ReadBlock +------------------------- +*/ + +int CBlockStream::ReadBlock( CBlock *get ) +{ + CBlockMember *bMember; + int b_id, numMembers; + unsigned char flags; + + if (!BlockAvailable()) + return false; + + b_id = GetInteger(); + numMembers = GetInteger(); + flags = (unsigned char) GetChar(); + + 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 ); + get->AddMember( bMember ); + } + +#ifdef _XBOX + Z_SetNewDeleteTemporary(false); +#endif + + return true; +} + +/* +------------------------- +Open +------------------------- +*/ + +int CBlockStream::Open( char *buffer, long size ) +{ + char id_header[sizeof(IBI_HEADER_ID)]; + float version; + + Init(); + + m_fileSize = size; + + m_stream = buffer; + + for ( int i = 0; i < sizeof( id_header ); i++ ) + { + id_header[i] = GetChar(); + } + + version = GetFloat(); + + //Check for valid header + if ( strcmp( id_header, IBI_HEADER_ID ) ) + { + Free(); + return false; + } + + //Check for valid version + if ( version != IBI_VERSION ) + { + Free(); + return false; + } + + return true; +} diff --git a/codemp/icarus/GameInterface.cpp b/codemp/icarus/GameInterface.cpp new file mode 100644 index 0000000..c085cd9 --- /dev/null +++ b/codemp/icarus/GameInterface.cpp @@ -0,0 +1,733 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// ICARUS Utility functions +//rww - mangled to work in server exe setting. + +//#include "Q3_Interface.h" +//#include "g_roff.h" +#include "../game/g_public.h" +#include "../server/server.h" +#include "interface.h" +#include "GameInterface.h" +#include "../qcommon/RoffSystem.h" +#include "Q3_Interface.h" + +ICARUS_Instance *iICARUS; +bufferlist_t ICARUS_BufferList; +entlist_t ICARUS_EntList; + +extern unsigned Com_BlockChecksum (const void *buffer, int length); +extern void Q3_DebugPrint( int level, const char *format, ... ); + +int ICARUS_entFilter = -1; + +/* +============= +ICARUS_GetScript + +gets the named script from the cache or disk if not already loaded +============= +*/ + +int ICARUS_GetScript( const char *name, char **buf ) +{ + bufferlist_t::iterator ei; + //Make sure the caller is valid + + //Attempt to retrieve a precached script + ei = ICARUS_BufferList.find( (char *) name ); + + //Not found, check the disk + if ( ei == ICARUS_BufferList.end() ) + { + if ( ICARUS_RegisterScript( name ) == false ) + return 0; + + //Script is now inserted, retrieve it and pass through + ei = ICARUS_BufferList.find( (char *) name ); + + if ( ei == ICARUS_BufferList.end() ) + { + //NOTENOTE: This is an internal error in STL if this happens... + assert(0); + return 0; + } + } + + *buf = (*ei).second->buffer; + return (*ei).second->length; +} + +/* +============= +ICARUS_RunScript + +Runs the script by the given name +============= +*/ +int ICARUS_RunScript( sharedEntity_t *ent, const char *name ) +{ + char *buf; + int len; + + //Make sure the caller is valid + if ( gSequencers[ent->s.number] == NULL ) + { + //Com_Printf( "%s : entity is not a valid script user\n", ent->classname ); + return false; + } +#ifdef _HACK_FOR_TESTING_ONLY_1 + char namex[1024]; + char *blah = strstr(name, "stu/"); + int r = blah - name; + if (blah) + { + int i = 0; + while (i < r) + { + namex[i] = name[i]; + i++; + } + namex[i] = 0; + strcat(namex, "ignorethisfolder/"); + i = strlen(namex); + while (name[r] != '/') + { + r++; + } + r++; + + while (name[r]) + { + namex[i] = name[r]; + r++; + i++; + } + namex[i] = 0; + } + else + { + strcpy(namex, name); + } + + len = ICARUS_GetScript (namex, &buf); +#else + len = ICARUS_GetScript (name, &buf); +#endif + if (len == 0) + { + return false; + } + + //Attempt to run the script + if S_FAILED(gSequencers[ent->s.number]->Run( buf, len )) + return false; + + if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == ent->s.number ) ) + { + Q3_DebugPrint( WL_VERBOSE, "%d Script %s executed by %s %s\n", svs.time, (char *) name, ent->classname, ent->targetname ); + } + + return true; +} + +/* +================= +ICARUS_Init + +Allocates a new ICARUS instance +================= +*/ + +void ICARUS_Init( void ) +{ + //Link all interface functions + Interface_Init( &interface_export ); + + //Create the ICARUS instance for this session + iICARUS = ICARUS_Instance::Create( &interface_export ); + + if ( iICARUS == NULL ) + { + Com_Error( ERR_DROP, "Unable to initialize ICARUS instance\n" ); + return; + } +} + +/* +================= +ICARUS_Shutdown + +Frees up ICARUS resources from all entities +================= +*/ + +void ICARUS_Shutdown( void ) +{ + bufferlist_t::iterator ei; + sharedEntity_t *ent = SV_GentityNum(0); + + //Release all ICARUS resources from the entities + for ( int i = 0; i < /*globals.num_entities*/MAX_GENTITIES; i++ ) + { + ent = SV_GentityNum(i); + + if (gSequencers[i]) + { + if (ent->s.number >= MAX_GENTITIES || + ent->s.number < 0) + { + ent->s.number = i; + assert(0); + } + ICARUS_FreeEnt( ent ); + } + } + + //Clear out all precached scripts + for ( ei = ICARUS_BufferList.begin(); ei != ICARUS_BufferList.end(); ei++ ) + { + //gi.Free( (*ei).second->buffer ); + ICARUS_Free((*ei).second->buffer); + delete (*ei).second; + } + + ICARUS_BufferList.clear(); + + //Clear the name map + ICARUS_EntList.clear(); + + //Free this instance + if ( iICARUS ) + { + iICARUS->Delete(); + iICARUS = NULL; + } +} + +/* +============== +ICARUS_FreeEnt + +Frees all ICARUS resources on an entity + +WARNING!!! DO NOT DO THIS WHILE RUNNING A SCRIPT, ICARUS WILL CRASH!!! +FIXME: shouldn't ICARUS handle this internally? + +============== +*/ +void ICARUS_FreeEnt( sharedEntity_t *ent ) +{ + assert( iICARUS ); + + if (ent->s.number >= MAX_GENTITIES || + ent->s.number < 0) + { + assert(0); + return; + } + + //Make sure the ent is valid + if ( gSequencers[ent->s.number] == NULL ) + return; + + //Remove them from the ICARUSE_EntList list so that when their g_entity index is reused, ICARUS doesn't try to affect the new (incorrect) ent. + if VALIDSTRING( ent->script_targetname ) + { + char temp[1024]; + + strncpy( (char *) temp, ent->script_targetname, 1023 ); + temp[ 1023 ] = 0; + + entlist_t::iterator it = ICARUS_EntList.find( Q_strupr(temp) ); + + if (it != ICARUS_EntList.end()) + { + ICARUS_EntList.erase(it); + } + } + + //Delete the sequencer and the task manager + iICARUS->DeleteSequencer( gSequencers[ent->s.number] ); + + //Clean up the pointers + gSequencers[ent->s.number] = NULL; + gTaskManagers[ent->s.number] = NULL; +} + + +/* +============== +ICARUS_ValidEnt + +Determines whether or not an entity needs ICARUS information +============== +*/ + +bool ICARUS_ValidEnt( sharedEntity_t *ent ) +{ + int i; + + //Targeted by a script + if VALIDSTRING( ent->script_targetname ) + return true; + + //Potentially able to call a script + for ( i = 0; i < NUM_BSETS; i++ ) + { + if VALIDSTRING( ent->behaviorSet[i] ) + { + //Com_Printf( "WARNING: Entity %d (%s) has behaviorSet but no script_targetname -- using targetname\n", ent->s.number, ent->targetname ); + + //ent->script_targetname = ent->targetname; + //rww - You CANNOT do things like this now. We're switching memory around to be able to read this memory from vm land, + //and while this allows us to read it on our "fake" entity here, we can't modify pointers like this. We can however do + //something completely hackish such as the following. + assert(ent->s.number >= 0 && ent->s.number < MAX_GENTITIES); + sharedEntity_t *trueEntity = SV_GentityNum(ent->s.number); + //This works because we're modifying the actual shared game vm data and turning one pointer into another. + //While these pointers both look like garbage to us in here, they are not. + trueEntity->script_targetname = trueEntity->targetname; + return true; + } + } + + return false; +} + +/* +============== +ICARUS_AssociateEnt + +Associate the entity's id and name so that it can be referenced later +============== +*/ + +void ICARUS_AssociateEnt( sharedEntity_t *ent ) +{ + char temp[1024]; + + if ( VALIDSTRING( ent->script_targetname ) == false ) + return; + + strncpy( (char *) temp, ent->script_targetname, 1023 ); + temp[ 1023 ] = 0; + + ICARUS_EntList[ Q_strupr( (char *) temp ) ] = ent->s.number; +} + +/* +============== +ICARUS_RegisterScript + +Loads and caches a script +============== +*/ + +bool ICARUS_RegisterScript( const char *name, qboolean bCalledDuringInterrogate /* = false */ ) +{ + bufferlist_t::iterator ei; + pscript_t *pscript; + char newname[MAX_FILENAME_LENGTH]; + char *buffer = NULL; // lose compiler warning about uninitialised vars + long length; + + //Make sure this isn't already cached + ei = ICARUS_BufferList.find( (char *) name ); + + // note special return condition here, if doing interrogate and we already have this file then we MUST return + // false (which stops the interrogator proceeding), this not only saves some time, but stops a potential + // script recursion bug which could lock the program in an infinite loop... Return TRUE for normal though! + // + if ( ei != ICARUS_BufferList.end() ) + return (bCalledDuringInterrogate)?false:true; + + sprintf((char *) newname, "%s%s", name, IBI_EXT ); + + + // 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... + // + 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 + 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 + + length = qbIgnoreFileRead ? -1 : FS_ReadFile( newname, (void **) &buffer ); + + if ( length <= 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 false; + } + + pscript = new pscript_t; + + pscript->buffer = (char *) ICARUS_Malloc(length);//gi.Malloc(length, TAG_ICARUS, qfalse); + memcpy (pscript->buffer, buffer, length); + pscript->length = length; + + FS_FreeFile( buffer ); + + ICARUS_BufferList[ name ] = pscript; + + return true; +} + +void ICARUS_SoundPrecache(const char *filename) +{ + T_G_ICARUS_SOUNDINDEX *sharedMem = (T_G_ICARUS_SOUNDINDEX *)sv.mSharedMemory; + + strcpy(sharedMem->filename, filename); + + VM_Call(gvm, GAME_ICARUS_SOUNDINDEX); +} + +int ICARUS_GetIDForString( const char *string ) +{ + T_G_ICARUS_GETSETIDFORSTRING *sharedMem = (T_G_ICARUS_GETSETIDFORSTRING *)sv.mSharedMemory; + + strcpy(sharedMem->string, string); + + return VM_Call(gvm, GAME_ICARUS_GETSETIDFORSTRING); +} + +/* +------------------------- +ICARUS_InterrogateScript +------------------------- +*/ + +// at this point the filename should have had the "scripts" (Q3_SCRIPT_DIR) added to it (but not the IBI extension) +// +void ICARUS_InterrogateScript( const char *filename ) +{ + CBlockStream stream; + CBlockMember *blockMember; + CBlock block; + + if (!Q_stricmp(filename,"NULL") || !Q_stricmp(filename,"default")) + return; + + ////////////////////////////////// + // + // ensure "scripts" (Q3_SCRIPT_DIR), which will be missing if this was called recursively... + // + char sFilename[MAX_FILENAME_LENGTH]; // should really be MAX_QPATH (and 64 bytes instead of 1024), but this fits the rest of the code + + if (!Q_stricmpn(filename,Q3_SCRIPT_DIR,strlen(Q3_SCRIPT_DIR))) + { + Q_strncpyz(sFilename,filename,sizeof(sFilename)); + } + else + { + Q_strncpyz(sFilename,va("%s/%s",Q3_SCRIPT_DIR,filename),sizeof(sFilename)); + } + // + ////////////////////////////////// + + + //Attempt to register this script + if ( ICARUS_RegisterScript( sFilename, qtrue ) == false ) // true = bCalledDuringInterrogate + return; + + char *buf; + long len; + + //Attempt to retrieve the new script data + if ( ( len = ICARUS_GetScript ( sFilename, &buf ) ) == 0 ) + return; + + //Open the stream + if ( stream.Open( buf, len ) == qfalse ) + return; + + const char *sVal1, *sVal2; + char temp[1024]; + int setID; + + //Now iterate through all blocks of the script, searching for keywords + while ( stream.BlockAvailable() ) + { + //Get a block + if ( stream.ReadBlock( &block ) == qfalse ) + 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 ); + + //we can do this I guess since the roff is loaded on the server. + theROFFSystem.Cache((char *)sVal1, qfalse); + } + } + break; + + case ID_PLAY: // to cache ROFF files + + sVal1 = (const char *) block.GetMemberData( 0 ); + + if (!Q_stricmp(sVal1,"PLAY_ROFF")) + { + sVal1 = (const char *) block.GetMemberData( 1 ); + + //we can do this I guess since the roff is loaded on the server. + theROFFSystem.Cache((char *)sVal1, qfalse); + } + break; + + //Run commands + case ID_RUN: + + sVal1 = (const char *) block.GetMemberData( 0 ); + + COM_StripExtension( sVal1, (char *) temp ); + ICARUS_InterrogateScript( (const char *) &temp ); + + break; + + case ID_SOUND: + //We can't just call over to S_RegisterSound or whatever because this is on the server. + sVal1 = (const char *) block.GetMemberData( 1 ); //0 is channel, 1 is filename + ICARUS_SoundPrecache(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 ); + + //Get the id for this set identifier + setID = ICARUS_GetIDForString( sVal1 ); + + //Check against valid types + switch ( setID ) + { + 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: + //Recursively obtain all embedded scripts + ICARUS_InterrogateScript( sVal2 ); + break; + case SET_LOOPSOUND: //like ID_SOUND, but set's looping + ICARUS_SoundPrecache(sVal2); + break; + case SET_VIDEO_PLAY: //in game cinematic + //do nothing for MP. + break; + case SET_ADDRHANDBOLT_MODEL: + case SET_ADDLHANDBOLT_MODEL: + //do nothing for MP + break; + default: + break; + } + } + break; + + default: + break; + } + + //Clean out the block for the next pass + block.Free(); + } + + //All done + stream.Free(); +} + +#ifdef _XBOX // We borrow the one in NPC_stats.c +extern stringID_table_t BSTable[]; +#else +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, +}; +#endif + +/* +============== +ICARUS_PrecacheEnt + +Precache all scripts being used by the entity +============== +*/ + +void ICARUS_PrecacheEnt( sharedEntity_t *ent ) +{ + char newname[MAX_FILENAME_LENGTH]; + int i; + + for ( i = 0; i < NUM_BSETS; i++ ) + { + if ( ent->behaviorSet[i] == NULL ) + continue; + + if ( GetIDForString( BSTable, ent->behaviorSet[i] ) == -1 ) + {//not a behavior set + sprintf((char *) newname, "%s/%s", Q3_SCRIPT_DIR, ent->behaviorSet[i] ); + + //Precache this, and all internally referenced scripts + ICARUS_InterrogateScript( newname ); + } + } +} + +/* +============== +ICARUS_InitEnt + +Allocates a sequencer and task manager only if an entity is a potential script user +============== +*/ + +void Q3_TaskIDClear( int *taskID ); +void ICARUS_InitEnt( sharedEntity_t *ent ) +{ + //Make sure this is a fresh ent + assert( iICARUS ); + assert( gTaskManagers[ent->s.number] == NULL ); + assert( gSequencers[ent->s.number] == NULL ); + + if ( gSequencers[ent->s.number] != NULL ) + return; + + if ( gTaskManagers[ent->s.number] != NULL ) + return; + + //Create the sequencer and setup the task manager + gSequencers[ent->s.number] = iICARUS->GetSequencer( ent->s.number ); + gTaskManagers[ent->s.number] = gSequencers[ent->s.number]->GetTaskManager(); + + //Initialize all taskIDs to -1 + memset( &ent->taskID, -1, sizeof( ent->taskID ) ); + + //Add this entity to a map of valid associated ents for quick retrieval later + ICARUS_AssociateEnt( ent ); + + //Precache all the entity's scripts + ICARUS_PrecacheEnt( ent ); +} + +/* +------------------------- +ICARUS_LinkEntity +------------------------- +*/ + +int ICARUS_LinkEntity( int entID, CSequencer *sequencer, CTaskManager *taskManager ) +{ + sharedEntity_t *ent = SV_GentityNum(entID); + + if ( ent == NULL ) + return false; + + gSequencers[ent->s.number] = sequencer; + gTaskManagers[ent->s.number] = taskManager; + + ICARUS_AssociateEnt( ent ); + + return true; +} + +/* +------------------------- +Svcmd_ICARUS_f +------------------------- +*/ + +void Svcmd_ICARUS_f( void ) +{ + //rwwFIXMEFIXME: Do something with this for debugging purposes at some point. + /* + char *cmd = Cmd_Argv( 1 ); + + if ( Q_stricmp( cmd, "log" ) == 0 ) + { + //g_ICARUSDebug->integer = WL_DEBUG; + if ( VALIDSTRING( Cmd_Argv( 2 ) ) ) + { + sharedEntity_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) ); + + ICARUS_entFilter = ( ent->s.number == ICARUS_entFilter ) ? -1 : ent->s.number; + + return; + } + + Com_Printf("Logging ICARUS info for all entities\n"); + + return; + } + */ + return; +} diff --git a/codemp/icarus/GameInterface.h b/codemp/icarus/GameInterface.h new file mode 100644 index 0000000..5b63e49 --- /dev/null +++ b/codemp/icarus/GameInterface.h @@ -0,0 +1,36 @@ +#ifndef __G_ICARUS_H__ +#define __G_ICARUS_H__ + +typedef struct pscript_s +{ + char *buffer; + long length; +} pscript_t; + +typedef map < string, int, less, allocator > entlist_t; +typedef map < string, pscript_t*, less, allocator > bufferlist_t; + +//ICARUS includes +extern interface_export_t interface_export; + +extern void Interface_Init( interface_export_t *pe ); +extern int ICARUS_RunScript( sharedEntity_t *ent, const char *name ); +extern bool ICARUS_RegisterScript( const char *name, qboolean bCalledDuringInterrogate = qfalse); +extern ICARUS_Instance *iICARUS; +extern bufferlist_t ICARUS_BufferList; +extern entlist_t ICARUS_EntList; + +// +// g_ICARUS.cpp +// +void ICARUS_Init( void ); +bool ICARUS_ValidEnt( sharedEntity_t *ent ); +void ICARUS_InitEnt( sharedEntity_t *ent ); +void ICARUS_FreeEnt( sharedEntity_t *ent ); +void ICARUS_AssociateEnt( sharedEntity_t *ent ); +void ICARUS_Shutdown( void ); +void Svcmd_ICARUS_f( void ); + +extern int ICARUS_entFilter; + +#endif//#ifndef __G_ICARUS_H__ diff --git a/codemp/icarus/Instance.cpp b/codemp/icarus/Instance.cpp new file mode 100644 index 0000000..3a89f63 --- /dev/null +++ b/codemp/icarus/Instance.cpp @@ -0,0 +1,655 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// ICARUS Instance +// +// -- jweier + +// this include must remain at the top of every Icarus CPP file +#include "icarus.h" + +#include + +class CSequencer; +class CTaskManager; + +//We can't put these on entity fields since all that stuff is in C +//which can't be changed due to VMs. So we'll use a global array +//and access by the entity index given. +CSequencer *gSequencers[MAX_GENTITIES]; +CTaskManager *gTaskManagers[MAX_GENTITIES]; + +// Instance + +ICARUS_Instance::ICARUS_Instance( void ) +{ + m_GUID = 0; + + //to be safe + memset(gSequencers,0, sizeof(gSequencers)); + memset(gTaskManagers,0, sizeof(gTaskManagers)); + +#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 + +} + +ICARUS_Instance::~ICARUS_Instance( void ) +{ +} + +/* +------------------------- +Create +------------------------- +*/ + +ICARUS_Instance *ICARUS_Instance::Create( interface_export_t *ie ) +{ + ICARUS_Instance *instance = new ICARUS_Instance; + instance->m_interface = ie; +#ifndef __linux__ + OutputDebugString( "ICARUS Instance successfully created\n" ); +#endif + return instance; +} + +/* +------------------------- +Free +------------------------- +*/ + +int ICARUS_Instance::Free( void ) +{ + sequencer_l::iterator sri; + + //Delete any residual sequencers + STL_ITERATE( sri, m_sequencers ) + { + delete (*sri); + +#ifdef _DEBUG + + m_DEBUG_NumSequencerResidual++; + +#endif + + } + + m_sequencers.clear(); + //all these are deleted now so clear the global map. + memset(gSequencers,0, sizeof(gSequencers)); + memset(gTaskManagers,0, sizeof(gTaskManagers)); + m_signals.clear(); + + sequence_l::iterator si; + + //Delete any residual sequences + STL_ITERATE( si, m_sequences ) + { + delete (*si); + +#ifdef _DEBUG + + m_DEBUG_NumSequenceResidual++; + +#endif + + } + + m_sequences.clear(); + + return true; +} + +/* +------------------------- +Delete +------------------------- +*/ + +int ICARUS_Instance::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 + + delete this; + + return true; +} + +/* +------------------------- +GetSequencer +------------------------- +*/ + +CSequencer *ICARUS_Instance::GetSequencer( int ownerID ) +{ + CSequencer *sequencer = CSequencer::Create(); + CTaskManager *taskManager = CTaskManager::Create(); + + sequencer->Init( ownerID, m_interface, taskManager, this ); + + taskManager->Init( sequencer ); + + STL_INSERT( m_sequencers, sequencer ); + +#ifdef _DEBUG + + m_DEBUG_NumSequencerAlloc++; + +#endif + + return sequencer; +} + +/* +------------------------- +DeleteSequencer +------------------------- +*/ + +void ICARUS_Instance::DeleteSequencer( CSequencer *sequencer ) +{ + // added 2/12/2 to properly delete blocks that were passed to the task manager + sequencer->Recall(); + + CTaskManager *taskManager = sequencer->GetTaskManager(); + + if ( taskManager ) + { + taskManager->Free(); + delete taskManager; + } + + m_sequencers.remove( sequencer ); + + sequencer->Free(); + delete sequencer; + +#ifdef _DEBUG + + m_DEBUG_NumSequencerFreed++; + +#endif + +} + +/* +------------------------- +GetSequence +------------------------- +*/ + +CSequence *ICARUS_Instance::GetSequence( void ) +{ + CSequence *sequence = CSequence::Create(); + + //Assign the GUID + sequence->SetID( m_GUID++ ); + sequence->SetOwner( this ); + + STL_INSERT( m_sequences, sequence ); + +#ifdef _DEBUG + + m_DEBUG_NumSequenceAlloc++; + +#endif + + return sequence; +} + +/* +------------------------- +GetSequence +------------------------- +*/ + +CSequence *ICARUS_Instance::GetSequence( int id ) +{ + sequence_l::iterator si; + STL_ITERATE( si, m_sequences ) + { + if ( (*si)->GetID() == id ) + return (*si); + } + + return NULL; +} + +/* +------------------------- +DeleteSequence +------------------------- +*/ + +void ICARUS_Instance::DeleteSequence( CSequence *sequence ) +{ + m_sequences.remove( sequence ); + + delete sequence; + +#ifdef _DEBUG + + m_DEBUG_NumSequenceFreed++; + +#endif +} + +/* +------------------------- +AllocateSequences +------------------------- +*/ + +int ICARUS_Instance::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; +} + +/* +------------------------- +SaveSequenceIDTable +------------------------- +*/ + +int ICARUS_Instance::SaveSequenceIDTable( void ) +{ + //Save out the number of sequences to follow + int numSequences = m_sequences.size(); + m_interface->I_WriteSaveData( '#SEQ', &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(); + } + + m_interface->I_WriteSaveData( 'SQTB', idTable, sizeof( int ) * numSequences ); + + delete[] idTable; + + return true; +} + +/* +------------------------- +SaveSequences +------------------------- +*/ + +int ICARUS_Instance::SaveSequences( void ) +{ + //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; +} + +/* +------------------------- +SaveSequencers +------------------------- +*/ + +int ICARUS_Instance::SaveSequencers( void ) +{ + //Save out the number of sequences to follow + int numSequencers = m_sequencers.size(); + m_interface->I_WriteSaveData( '#SQR', &numSequencers, sizeof( numSequencers ) ); + + //The sequencers are then saved + sequencer_l::iterator si; + STL_ITERATE( si, m_sequencers ) + { + (*si)->Save(); + } + + return true; +} + +/* +------------------------- +SaveSignals +------------------------- +*/ + +int ICARUS_Instance::SaveSignals( void ) +{ + int numSignals = m_signals.size(); + + m_interface->I_WriteSaveData( 'ISIG', &numSignals, sizeof( numSignals ) ); + + signal_m::iterator si; + STL_ITERATE( si, m_signals ) + { + //m_interface->I_WriteSaveData( 'ISIG', &numSignals, sizeof( numSignals ) ); + const char *name = ((*si).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 + m_interface->I_WriteSaveData( 'SIG#', &length, sizeof ( length ) ); + + //Write out the string + m_interface->I_WriteSaveData( 'SIGN', (void *) name, length ); + } + + return true; +} + +/* +------------------------- +Save +------------------------- +*/ + +int ICARUS_Instance::Save( void ) +{ + //Save out a ICARUS save block header with the ICARUS version + double version = ICARUS_VERSION; + m_interface->I_WriteSaveData( 'ICAR', &version, sizeof( version ) ); + + //Save out the signals + if ( SaveSignals() == false ) + return false; + + //Save out the sequences + if ( SaveSequences() == false ) + return false; + + //Save out the sequencers + if ( SaveSequencers() == false ) + return false; + + m_interface->I_WriteSaveData( 'IEND', &version, sizeof( version ) ); + + return true; +} + +/* +------------------------- +LoadSignals +------------------------- +*/ + +int ICARUS_Instance::LoadSignals( void ) +{ + int numSignals; + + m_interface->I_ReadSaveData( 'ISIG', &numSignals, sizeof( numSignals ) ); + + for ( int i = 0; i < numSignals; i++ ) + { + char buffer[1024]; + int length; + + //Get the size of the string + m_interface->I_ReadSaveData( 'SIG#', &length, sizeof( length ) ); + + assert( length < sizeof( buffer ) ); + + //Get the string + m_interface->I_ReadSaveData( 'SIGN', &buffer, length ); + + //Turn it on and add it to the system + Signal( (const char *) &buffer ); + } + + return true; +} + +/* +------------------------- +LoadSequence +------------------------- +*/ + +int ICARUS_Instance::LoadSequence( void ) +{ + CSequence *sequence = GetSequence(); + + //Load the sequence back in + sequence->Load(); + + //If this sequence had a higher GUID than the current, save it + if ( sequence->GetID() > m_GUID ) + m_GUID = sequence->GetID(); + + return true; +} + +/* +------------------------- +LoadSequence +------------------------- +*/ + +int ICARUS_Instance::LoadSequences( void ) +{ + CSequence *sequence; + int numSequences; + + //Get the number of sequences to read in + m_interface->I_ReadSaveData( '#SEQ', &numSequences, sizeof( numSequences ) ); + + int *idTable = new int[ numSequences ]; + + if ( idTable == NULL ) + return false; + + //Load the sequencer ID table + m_interface->I_ReadSaveData( 'SQTB', 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() ) == false ) + return false; + } + + //Free the idTable + delete[] idTable; + + return true; +} + +/* +------------------------- +LoadSequencers +------------------------- +*/ + +int ICARUS_Instance::LoadSequencers( void ) +{ + CSequencer *sequencer; + int numSequencers; + + //Get the number of sequencers to load + m_interface->I_ReadSaveData( '#SQR', &numSequencers, sizeof( &numSequencers ) ); + + //Load all sequencers + for ( int i = 0; i < numSequencers; i++ ) + { + //NOTENOTE: The ownerID will be replaced in the loading process + if ( ( sequencer = GetSequencer( -1 ) ) == NULL ) + return false; + + if ( sequencer->Load() == false ) + return false; + } + + return true; +} + +/* +------------------------- +Load +------------------------- +*/ + +int ICARUS_Instance::Load( void ) +{ + //Clear out any old information + Free(); + + //Check to make sure we're at the ICARUS save block + double version; + m_interface->I_ReadSaveData( 'ICAR', &version, sizeof( version ) ); + + //Versions must match! + if ( version != ICARUS_VERSION ) + { + m_interface->I_DPrintf( WL_ERROR, "save game data contains outdated ICARUS version information!\n"); + return false; + } + + //Load all signals + if ( LoadSignals() == false ) + { + m_interface->I_DPrintf( WL_ERROR, "failed to load signals from save game!\n"); + return false; + } + + //Load in all sequences + if ( LoadSequences() == false ) + { + m_interface->I_DPrintf( WL_ERROR, "failed to load sequences from save game!\n"); + return false; + } + + //Load in all sequencers + if ( LoadSequencers() == false ) + { + m_interface->I_DPrintf( WL_ERROR, "failed to load sequencers from save game!\n"); + return false; + } + + m_interface->I_ReadSaveData( 'IEND', &version, sizeof( version ) ); + + return true; +} + +/* +------------------------- +Signal +------------------------- +*/ + +void ICARUS_Instance::Signal( const char *identifier ) +{ + m_signals[ identifier ] = 1; +} + +/* +------------------------- +CheckSignal +------------------------- +*/ + +bool ICARUS_Instance::CheckSignal( const char *identifier ) +{ + signal_m::iterator smi; + + smi = m_signals.find( identifier ); + + if ( smi == m_signals.end() ) + return false; + + return true; +} + +/* +------------------------- +ClearSignal +------------------------- +*/ + +void ICARUS_Instance::ClearSignal( const char *identifier ) +{ + m_signals.erase( identifier ); +} diff --git a/codemp/icarus/Interface.cpp b/codemp/icarus/Interface.cpp new file mode 100644 index 0000000..a5e6fd2 --- /dev/null +++ b/codemp/icarus/Interface.cpp @@ -0,0 +1,24 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// ICARUS Engine Interface File +// +// This file is the only section of the ICARUS systems that +// is not directly portable from engine to engine. +// +// -- jweier + +#include "../game/g_public.h" + +#include "interface.h" +/* +void Interface_Init( interface_export_t *pe ) +{ + + //TODO: This is where you link up all your functions to the engine + + //Example: + // + // pe->I_GetEntityByName = ENGINE_GetEntityByName; +} +*/ diff --git a/codemp/icarus/Interpreter.cpp b/codemp/icarus/Interpreter.cpp new file mode 100644 index 0000000..9ef494e --- /dev/null +++ b/codemp/icarus/Interpreter.cpp @@ -0,0 +1,2506 @@ +// Token Interpreter +// +// -- jweier + +#include //For getcwd() +#include //For getch() +#include + +#include "Tokenizer.h" +#include "BlockStream.h" +#include "Interpreter.h" + +/* +=================================================================================================== + + Table Definitions + +=================================================================================================== +*/ + +//FIXME: The following tables should be passed in to the interpreter for flexibility + +//Symbol Table + +keywordArray_t CInterpreter::m_symbolKeywords[] = +{ + //Blocks + "{", TK_BLOCK_START, + "}", TK_BLOCK_END, + + //Vectors + "<", TK_VECTOR_START, + ">", TK_VECTOR_END, + + //Groups + "(", TK_OPEN_PARENTHESIS, + ")", TK_CLOSED_PARENTHESIS, + + "=", TK_EQUALS, + "!", TK_NOT, + + //End + "", TK_EOF, +}; + +keywordArray_t CInterpreter::m_conditionalKeywords[] = +{ + "", TK_EOF, +}; + +//ID Table + +keywordArray_t CInterpreter::m_IDKeywords[] = +{ + "AFFECT", ID_AFFECT, + "SOUND", ID_SOUND, + "MOVE", ID_MOVE, + "ROTATE", ID_ROTATE, + "WAIT", ID_WAIT, + "SET", ID_SET, + "LOOP", ID_LOOP, + "PRINT", ID_PRINT, + "TAG", ID_TAG, + "USE", ID_USE, + "FLUSH", ID_FLUSH, + "RUN", ID_RUN, + "KILL", ID_KILL, + "REMOVE", ID_REMOVE, + "CAMERA", ID_CAMERA, + "GET", ID_GET, + "RANDOM", ID_RANDOM, + "IF", ID_IF, + "ELSE", ID_ELSE, + "REM", ID_REM, + "FLOAT", TK_FLOAT, + "VECTOR", TK_VECTOR, + "STRING", TK_STRING, + "TASK", ID_TASK, + "DO", ID_DO, + "DECLARE", ID_DECLARE, + "FREE", ID_FREE, + "DOWAIT", ID_DOWAIT, + "SIGNAL", ID_SIGNAL, + "WAITSIGNAL", ID_WAITSIGNAL, + "PLAY", ID_PLAY, + + "", ID_EOF, +}; + +//Type Table + +keywordArray_t CInterpreter::m_typeKeywords[] = +{ + //Set types + "ANGLES", TYPE_ANGLES, + "ORIGIN", TYPE_ORIGIN, + + //Affect types + "INSERT", TYPE_INSERT, + "FLUSH", TYPE_FLUSH, + + //Get types + "FLOAT", TK_FLOAT, + "INT", TK_INT, + "VECTOR", TK_VECTOR, + "STRING", TK_STRING, + + "PAN", TYPE_PAN, + "ZOOM", TYPE_ZOOM, + "MOVE", TYPE_MOVE, + "FADE", TYPE_FADE, + "PATH", TYPE_PATH, + "ENABLE", TYPE_ENABLE, + "DISABLE", TYPE_DISABLE, + "SHAKE", TYPE_SHAKE, + "ROLL", TYPE_ROLL, + "TRACK", TYPE_TRACK, + "FOLLOW", TYPE_FOLLOW, + "DISTANCE", TYPE_DISTANCE, + + //End + "", TYPE_EOF, +}; + + +/* +=================================================================================================== + + Constructor / Destructor + +=================================================================================================== +*/ + +CInterpreter::CInterpreter() +{ +} + +CInterpreter::~CInterpreter() +{ +} + +/* +=================================================================================================== + + Error Handling + +=================================================================================================== +*/ + +int CInterpreter::Error( char *format, ... ) +{ + va_list argptr; + char *error_file, error_msg[1024]="", work_dir[1024]="", out_msg[1024]=""; + int error_line = m_iCurrentLine; // m_tokenizer->GetCurLine(); + + m_tokenizer->GetCurFilename( &error_file ); + if (!error_file) + { + // 99% of the time we'll get here now, because of pushed parse streams + // + error_file = (char *)m_sCurrentFile.c_str(); + } + + va_start (argptr, format); + vsprintf (error_msg, format, argptr); + va_end (argptr); + + strcpy((char *) work_dir, getcwd( (char *) &work_dir, 1024 ) ); + + if (error_file[1] == ':') + { + sprintf((char *) out_msg, "%s (%d) : error: %s\n", error_file, error_line, error_msg); + } + else + { + sprintf((char *) out_msg, "%s\\%s (%d) : error: %s\n", work_dir, error_file, error_line, error_msg); + } + + if (m_sCurrentLine.length()) + { + strcat(out_msg, "\nLine:\n\n"); + strcat(out_msg, m_sCurrentLine.c_str()); + strcat(out_msg, "\n"); + } + +#ifdef __POP_UPS__ + + MessageBox( NULL, out_msg, "Error", MB_OK ); + +#else + + printf(out_msg); + +#endif + + // A bit of kludge code that takes care of the case where there's some garbage at the beginning of the file + // before any blocks are read as valid. This is needed because ints are incapable of containing 0 + // + // This'll mean that technically it's saying block 1 is wrong, rather than the one in between them, but I can + // live with that. + // + if (m_iBadCBlockNumber == 0) + { + m_iBadCBlockNumber = 1; + } + return false; +} + +/* +=================================================================================================== + + Local Variable Functions + +=================================================================================================== +*/ + +/* +------------------------- +InitVars +------------------------- +*/ + +void CInterpreter::InitVars( void ) +{ + m_vars.clear(); + m_varMap.clear(); +} + +/* +------------------------- +FreeVars +------------------------- +*/ + +void CInterpreter::FreeVars( void ) +{ + variable_v::iterator vi; + + for ( vi = m_vars.begin(); vi != m_vars.end(); vi++ ) + { + delete (*vi); + } + + InitVars(); +} + +/* +------------------------- +AddVar +------------------------- +*/ + +variable_t *CInterpreter::AddVar( const char *name, int type ) +{ + variable_t *var; + + var = new variable_t; + + if ( var == NULL ) + return NULL; + + //Specify the type + var->type = type; + + //Retain the name internally + strncpy( (char *) var->name, name, MAX_VAR_NAME ); + + //Associate it + m_varMap[ name ] = var; + + return var; +} + +/* +------------------------- +FindVar +------------------------- +*/ + +variable_t *CInterpreter::FindVar( const char *name ) +{ + variable_m::iterator vmi; + + vmi = m_varMap.find( name ); + + if ( vmi == m_varMap.end() ) + return NULL; + + return (*vmi).second; +} + +/* +------------------------- +GetVariable +------------------------- +*/ + +int CInterpreter::GetVariable( int type ) +{ + const char *varName; + variable_t *var; + CToken *token; + + //Get the variable's name + token = m_tokenizer->GetToken( 0, 0 ); + varName = token->GetStringValue(); + + //See if we already have a variable by this name + var = FindVar( varName ); + + //Variable names must be unique on creation + if ( var ) + return Error( "\"%s\" : already exists\n", varName ); + + //Add the variable + AddVar( varName, type ); + + //Insert the variable into the stream + + CBlock block; + + block.Create( TYPE_VARIABLE ); + block.Write( TK_FLOAT, (float) type ); + block.Write( TK_STRING, varName ); + + m_blockStream->WriteBlock( &block ); + + token->Delete(); + + return true; +} + +/* +=================================================================================================== + + ID Table Functions + +=================================================================================================== +*/ + +int CInterpreter::GetVector( CBlock *block ) +{ + //Look for a tag + if ( MatchTag() ) + { + return GetTag( block ); + } + + //Look for a get + if ( MatchGet() ) + { + return GetGet( block ); + } + + if ( Match( TK_VECTOR_START ) ) + { + //Get the vector + block->Write( TK_VECTOR, (float) TK_VECTOR ); + + for (int i=0; i<3; i++) + GetFloat( block ); + + if (!Match( TK_VECTOR_END )) + { + return Error("syntax error : expected end of vector"); + } + + return true; + } + + return false; +} + +/* +=================================================================================================== + + MatchTag() + + Attempts to match to a tag identifier. + +=================================================================================================== +*/ + +int CInterpreter::MatchTag( void ) +{ + CToken *token; + const char *idName; + int id; + + token = m_tokenizer->GetToken( 0, 0 ); + idName = token->GetStringValue(); + id = FindSymbol( idName, m_IDKeywords ); + + if ( id != ID_TAG ) + { + //Return the token + m_tokenizer->PutBackToken( token ); + return false; + } + + token->Delete(); + return true; +} + +/* +=================================================================================================== + + MatchGet() + + Attempts to match to a get identifier. + +=================================================================================================== +*/ + +int CInterpreter::MatchGet( void ) +{ + CToken *token; + const char *idName; + int id; + + token = m_tokenizer->GetToken( 0, 0 ); + idName = token->GetStringValue(); + id = FindSymbol( idName, m_IDKeywords ); + + if ( id != ID_GET ) + { + //Return the token + m_tokenizer->PutBackToken( token ); + return false; + } + + token->Delete(); + return true; +} + +/* +=================================================================================================== + + MatchRandom() + + Attempts to match to a random identifier. + +=================================================================================================== +*/ + +int CInterpreter::MatchRandom( void ) +{ + CToken *token; + const char *idName; + int id; + + token = m_tokenizer->GetToken( 0, 0 ); + idName = token->GetStringValue(); + id = FindSymbol( idName, m_IDKeywords ); + + if ( id != ID_RANDOM ) + { + //Return the token + m_tokenizer->PutBackToken( token ); + return false; + } + + token->Delete(); + return true; +} + +/* +=================================================================================================== + + FindSymbol() + + Searches the symbol table for the given name. Returns the ID if found. + +=================================================================================================== +*/ + +int CInterpreter::FindSymbol( const char *name, keywordArray_t *table) +{ + keywordArray_t *ids; + + for (ids = table; (strcmp(ids->m_keyword, "")); ids++) + { + if (!stricmp(name, ids->m_keyword)) + return ids->m_tokenvalue; + } + + return -1; +} + + +/* +=================================================================================================== + + Match() + + Looks ahead to the next token to try and match it to the passed token, consumes token on success. + +=================================================================================================== +*/ + +//NOTENOTE: LookAhead() was separated from Match() for clarity + +int CInterpreter::Match( int token_id ) +{ + CToken *token; + + token = m_tokenizer->GetToken( 0, 0 ); + + if ( token->GetType() != token_id ) + { + //This may have been a check, so don't loose the token + m_tokenizer->PutBackToken( token ); + + return false; + } + + return true; +} + +/* +------------------------- +GetNextType +------------------------- +*/ + +int CInterpreter::GetNextType( void ) +{ + CToken *token = m_tokenizer->GetToken( 0, 0 ); + int id = token->GetType(); + + m_tokenizer->PutBackToken( token ); + + return id; +} + +/* +=================================================================================================== + + LookAhead() + + Looks ahead without consuming on success. + +=================================================================================================== +*/ + +int CInterpreter::LookAhead( int token_id ) +{ + CToken *token; + + token = m_tokenizer->GetToken( 0, 0 ); + + if ( token->GetType() != token_id ) + { + m_tokenizer->PutBackToken( token ); + + return false; + } + + m_tokenizer->PutBackToken( token ); + + return true; +} + +/* +=================================================================================================== + + GetTokenName() + + Returns the name of a token. + +=================================================================================================== +*/ + +const char *CInterpreter::GetTokenName( int token_id ) +{ + switch ( token_id ) + { + case TK_STRING: + return "STRING"; + break; + + case TK_CHAR: + return "CHARACTER"; + break; + + case TK_IDENTIFIER: + return "IDENTIFIER"; + break; + + case TK_FLOAT: + return "FLOAT"; + break; + + case TK_INTEGER: + return "INTEGER"; + break; + + default: + return "UNKNOWN"; + break; + } +} + +/* +=================================================================================================== + + Token Value Functions + +=================================================================================================== +*/ + +/* +=================================================================================================== + + GetFloat() + + Attempts to match and retrieve the value of a float token. + +=================================================================================================== +*/ + +int CInterpreter::GetFloat( CBlock *block ) +{ + CToken *token; + int type; + + //Look for a get + if ( MatchGet() ) + { + return GetGet( block ); + } + + //Look for a random + if ( MatchRandom() ) + { + return GetRandom( block ); + } + + token = m_tokenizer->GetToken(0,0); + type = token->GetType(); + + //Floats can accept either int or float values + if ( ( type != TK_FLOAT ) && ( type != TK_INT ) ) + { + return Error("syntax error : expected float; found %s", GetTokenName(type) ); + } + + if (type == TK_FLOAT) + { + block->Write( TK_FLOAT, (float) token->GetFloatValue() ); + } + else + { + block->Write( TK_FLOAT, (float) token->GetIntValue() ); + } + + token->Delete(); + + return true; +} + +/* +=================================================================================================== + + GetInteger() + + Attempts to match and retrieve the value of an integer token. + +=================================================================================================== +*/ + +int CInterpreter::GetInteger( CBlock *block ) +{ + return GetFloat( block ); +} + +/* +=================================================================================================== + + GetString() + + Attempts to match and retrieve the value of a string token. + +=================================================================================================== +*/ + +int CInterpreter::GetString( CBlock *block ) +{ + CToken *token; + int type; + + //Look for a get + if ( MatchGet() ) + { + return GetGet( block ); + } + + //Look for a random + if ( MatchRandom() ) + { + return GetRandom( block ); + } + + token = m_tokenizer->GetToken(0, 0); + type = token->GetType(); + + if ( (type != TK_STRING) && (type != TK_CHAR) ) + { + return Error("syntax error : expected string; found %s", GetTokenName(type)); + } + +//UGLY HACK!!! + + const char *temptr; + char temp[1024]; + + temptr = token->GetStringValue(); + + if ( strlen(temptr)+1 > sizeof( temp ) ) + { + return false; + } + + for ( int i = 0; i < (int)strlen( temptr ); i++ ) + { + if ( temptr[i] == '#' ) + temp[i] = '\n'; + else + temp[i] = temptr[i]; + } + + temp[ strlen( temptr ) ] = 0; + +//UGLY HACK END!!! + + block->Write( TK_STRING, (const char *) &temp ); + + token->Delete(); + + return true; +} + +/* +=================================================================================================== + + GetIdentifier() + + Attempts to match and retrieve the value of an indentifier token. + +=================================================================================================== +*/ + +int CInterpreter::GetIdentifier( CBlock *block ) +{ + CToken *token; + int type; + + //FIXME: Should identifiers do this? + if ( MatchGet() ) + { + if ( GetGet( block ) == false ) + return false; + + return true; + } + + token = m_tokenizer->GetToken(0, 0); + type = token->GetType(); + + if ( type != TK_IDENTIFIER ) + { + return Error("syntax error : expected indentifier; found %s", GetTokenName(type)); + } + + block->Write( TK_IDENTIFIER, (const char *) token->GetStringValue() ); + + token->Delete(); + + return true; +} + +/* +=================================================================================================== + + GetEvaluator() + + Attempts to match and retrieve the value of an evaluator token. + +=================================================================================================== +*/ + +int CInterpreter::GetEvaluator( CBlock *block ) +{ + CToken *token; + int type; + + if ( MatchGet() ) + return false; + + if ( MatchRandom() ) + return false; + + token = m_tokenizer->GetToken(0, 0); + type = token->GetType(); + token->Delete(); + + switch ( type ) + { + case TK_GREATER_THAN: + case TK_LESS_THAN: + case TK_EQUALS: + case TK_NOT: + break; + + case TK_VECTOR_START: + type = TK_LESS_THAN; + break; + + case TK_VECTOR_END: + type = TK_GREATER_THAN; + break; + + default: + return Error("syntax error : expected operator type, found %s", GetTokenName( type ) ); + } + + block->Write( type, 0 ); + + return true; +} + +/* +=================================================================================================== + + GetAny() + + Attempts to match and retrieve any valid data type. + +=================================================================================================== +*/ + +int CInterpreter::GetAny( CBlock *block ) +{ + CToken *token; + int type; + + if ( MatchGet() ) + { + if ( GetGet( block ) == false ) + return false; + + return true; + } + + if ( MatchRandom() ) + { + if ( GetRandom( block ) == false ) + return false; + + return true; + } + + if ( MatchTag() ) + { + if ( GetTag( block ) == false ) + return false; + + return true; + } + + token = m_tokenizer->GetToken(0, 0); + type = token->GetType(); + + switch ( type ) + { + case TK_FLOAT: + m_tokenizer->PutBackToken( token ); + if ( GetFloat( block ) == false ) + return false; + + break; + + case TK_INT: + m_tokenizer->PutBackToken( token ); + if ( GetInteger( block ) == false ) + return false; + + break; + + case TK_VECTOR_START: + m_tokenizer->PutBackToken( token ); + if ( GetVector( block ) == false ) + return false; + + break; + + case TK_STRING: + case TK_CHAR: + m_tokenizer->PutBackToken( token ); + if ( GetString( block ) == false ) + return false; + + break; + + case TK_IDENTIFIER: + m_tokenizer->PutBackToken( token ); + if ( GetIdentifier( block ) == false ) + return false; + + break; + + default: + return false; + } + + return true; +} + +/* +=================================================================================================== + + GetType() + + Attempts to match and retrieve the value of a type token. + +=================================================================================================== +*/ + +int CInterpreter::GetType( char *get ) +{ + CToken *token; + char *string; + int type; + + token = m_tokenizer->GetToken(0, 0); + type = token->GetType(); + + if ( type != TK_IDENTIFIER ) + { + return Error("syntax error : expected identifier; found %s", GetTokenName(type)); + } + + string = (char *) token->GetStringValue(); + + if ( (strlen(string) + 1) > MAX_STRING_LENGTH) + { + Error("string exceeds 256 character limit"); + return false; + } + + strcpy(get, string); + + return true; +} + +/* +=================================================================================================== + + GetID() + + Attempts to match and interpret an identifier. + +=================================================================================================== +*/ + +//FIXME: This should use an externally defined table to match ID and functions + +int CInterpreter::GetID( char *id_name ) +{ + int id; + + id = FindSymbol( id_name, m_IDKeywords ); + + if ( id == -1 ) + return Error("'%s' : unknown identifier", id_name); + + //FIXME: Function pointers would be awfully nice.. but not inside a class! Weee!! + + switch (id) + { + + //Affect takes control of an entity + + case ID_AFFECT: + return GetAffect(); + break; + + //Wait for a specified amount of time + + case ID_WAIT: + return GetWait(); + break; + + //Generic set call + + case ID_SET: + return GetSet(); + break; + + case ID_LOOP: + return GetLoop(); + break; + + case ID_PRINT: + return GetPrint(); + break; + + case ID_USE: + return GetUse(); + break; + + case ID_FLUSH: + return GetFlush(); + break; + + case ID_RUN: + return GetRun(); + break; + + case ID_KILL: + return GetKill(); + break; + + case ID_REMOVE: + return GetRemove(); + break; + + case ID_CAMERA: + return GetCamera(); + break; + + case ID_SOUND: + return GetSound(); + break; + + case ID_MOVE: + return GetMove(); + break; + + case ID_ROTATE: + return GetRotate(); + break; + + case ID_IF: + return GetIf(); + break; + + case ID_ELSE: + //return Error("syntax error : else without matching if"); + return GetElse(); //FIXME: Protect this call so that floating else's aren't allowed + break; + + case ID_GET: + return Error("syntax error : illegal use of \"get\""); + break; + + case ID_TAG: + return Error("syntax error : illegal use of \"tag\""); + break; + + case ID_TASK: + return GetTask(); + break; + + case ID_DO: + return GetDo(); + break; + + case ID_DECLARE: + return GetDeclare(); + break; + + case ID_FREE: + return GetFree(); + break; + + case ID_REM: + GetRem(); + break; + + case ID_DOWAIT: + GetDoWait(); + break; + + case ID_SIGNAL: + GetSignal(); + break; + + case ID_WAITSIGNAL: + GetWaitSignal(); + break; + + case ID_PLAY: + GetPlay(); //Bad eighties slang joke... yeah, it's not really funny, I know... + break; + + //Local variable types + case TK_FLOAT: + case TK_INT: + case TK_STRING: + case TK_VECTOR: + GetVariable( id ); + break; + + //Unknown ID + + default: + case -1: + return Error("'%s' : unknown identifier", id_name); + break; + } + + return true; +} + +/* +=================================================================================================== + + ID Interpreting Functions + +=================================================================================================== +*/ + +/* +------------------------- +GetDeclare +------------------------- +*/ + +int CInterpreter::GetDeclare( void ) +{ + CBlock block; + char typeName[MAX_STRING_LENGTH]; + int type; + + block.Create( ID_DECLARE ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetType( (char *) typeName ) == false ) + return false; + + type = FindSymbol( typeName, m_typeKeywords); + + switch ( type ) + { + case TK_FLOAT: + case TK_VECTOR: + case TK_STRING: + block.Write( TK_FLOAT, (float) type ); + break; + + default: + return Error("unknown identifier %s", typeName ); + break; + } + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("declare : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + + +/* +------------------------- +GetFree +------------------------- +*/ + +int CInterpreter::GetFree( void ) +{ + CBlock block; + + block.Create( ID_FREE ); + + if ( Match( TK_OPEN_PARENTHESIS ) == false ) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if ( Match( TK_CLOSED_PARENTHESIS ) == false ) + return Error("free : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetIf() + + Handles the if() conditional statement. + +=================================================================================================== +*/ + +// if ( STRING ? STRING ) + +int CInterpreter::GetIf( void ) +{ + CBlock block; + + block.Create( ID_IF ); + + if ( Match( TK_OPEN_PARENTHESIS ) == false ) + return Error("syntax error : '(' not found"); + + if ( GetAny( &block ) == false ) + return false; + + if ( GetEvaluator( &block ) == false ) + return false; + + if ( GetAny( &block ) == false ) + return false; + + if ( Match( TK_CLOSED_PARENTHESIS ) == false ) + return Error("if : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetElse() + + Handles the else() conditional statement. + +=================================================================================================== +*/ + +// else + +int CInterpreter::GetElse( void ) +{ + CBlock block; + + block.Create( ID_ELSE ); + + /* + if ( Match( TK_OPEN_PARENTHESIS ) == false ) + return Error("syntax error : '(' not found"); + */ + + /* + if ( GetAny( &block ) == false ) + return false; + + if ( GetEvaluator( &block ) == false ) + return false; + + if ( GetAny( &block ) == false ) + return false; + */ + + /* + if ( Match( TK_CLOSED_PARENTHESIS ) == false ) + return Error("sound : too many parameters"); + */ + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetTask() + + Handles the task() sequence specifier. + +=================================================================================================== +*/ + +//task ( name ) { } + +int CInterpreter::GetTask( void ) +{ + CBlock block; + + block.Create( ID_TASK ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("GetTask: too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; + +} + +/* +=================================================================================================== + + GetDo() + + Handles the do() function. + +=================================================================================================== +*/ + +//do ( taskName ) + +int CInterpreter::GetDo( void ) +{ + CBlock block; + + block.Create( ID_DO ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("do : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetGet() + + Handles the get() function. + +=================================================================================================== +*/ + +// get( TYPE, NAME ); + +int CInterpreter::GetGet( CBlock *block ) +{ + char typeName[MAX_STRING_LENGTH]; + int type; + + block->Write( ID_GET, (float) ID_GET ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetType( (char *) typeName ) == false ) + return false; + + type = FindSymbol( typeName, m_typeKeywords); + + switch ( type ) + { + case TK_FLOAT: + case TK_INT: + case TK_VECTOR: + case TK_STRING: + block->Write( TK_FLOAT, (float) type ); + break; + + default: + return Error("unknown identifier %s", typeName ); + break; + } + + if ( GetString( block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("affect : too many parameters"); + + return true; +} + +/* +=================================================================================================== + + GetRandom() + + Handles the random() function. + +=================================================================================================== +*/ + +// random( low, high ); + +int CInterpreter::GetRandom( CBlock *block ) +{ + block->Write( ID_RANDOM, (float) ID_RANDOM ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetFloat( block ) == false ) + return false; + + if ( GetFloat( block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("affect : too many parameters"); + + return true; +} + +/* +=================================================================================================== + + GetSound() + + Handles the sound() function. + +=================================================================================================== +*/ + +// sound( NAME ); + +int CInterpreter::GetSound( void ) +{ + CBlock block; + + block.Create( ID_SOUND ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetIdentifier( &block ) == false ) + return false; + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("sound : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetMove() + + Handles the move() function. + +=================================================================================================== +*/ + +// move( ORIGIN, ANGLES, DURATION ); + +int CInterpreter::GetMove( void ) +{ + CBlock block; + + block.Create( ID_MOVE ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetVector( &block ) == false ) + return false; + + //Angles are optional + if ( LookAhead( TK_VECTOR_START ) || LookAhead( TK_IDENTIFIER ) ) + { + if ( GetVector( &block ) == false ) + return false; + } + + if ( GetFloat( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("move : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetRotate() + + Handles the rotate() function. + +=================================================================================================== +*/ + +// move( ANGLES, DURATION ); + +int CInterpreter::GetRotate( void ) +{ + CBlock block; + + block.Create( ID_ROTATE ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetVector( &block ) == false ) + return false; + + if ( GetFloat( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("move : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetAffect() + + Handles the affect() function. + +=================================================================================================== +*/ + +//FIXME: This should be externally defined + +int CInterpreter::GetAffect( void ) +{ + CBlock block; + char typeName[MAX_STRING_SIZE]; + int type; + + block.Create( ID_AFFECT ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!LookAhead( TK_IDENTIFIER )) + return Error("syntax error : identifier not found"); + + if ( MatchGet() ) + return Error("syntax error : illegal use of \"get\""); + + if ( GetType( (char *) typeName ) == false ) + return false; + + type = FindSymbol( typeName, m_typeKeywords); + + switch ( type ) + { + case TYPE_INSERT: + case TYPE_FLUSH: + + block.Write( TK_FLOAT, (float) type ); + break; + + default: + return Error("'%s': unknown affect type", typeName ); + break; + + } + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("affect : too many parameters"); + + if (!LookAhead( TK_BLOCK_START )) + return Error("syntax error : '{' not found"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetWait() + + Handles the wait() function. + +=================================================================================================== +*/ + +//FIXME: This should be externally defined + +int CInterpreter::GetWait( void ) +{ + CBlock block; + + block.Create( ID_WAIT ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( LookAhead( TK_STRING ) ) + { + if ( GetString( &block ) == false ) + return false; + } + else + { + if ( GetFloat( &block ) == false ) + return false; + } + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("wait : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetSet() + + Handles the set() function. + +=================================================================================================== +*/ + +//FIXME: This should be externally defined + +int CInterpreter::GetSet( void ) +{ + CBlock block; + + block.Create( ID_SET ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + //Check for get placement + if ( MatchGet() ) + { + if ( GetGet( &block ) == false ) + return false; + } + else + { + switch( GetNextType() ) + { + case TK_INT: + + if ( GetInteger( &block ) == false ) + return false; + + break; + + case TK_FLOAT: + + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TK_STRING: + + if ( GetString( &block ) == false ) + return false; + + break; + + case TK_VECTOR_START: + + if ( GetVector( &block ) == false ) + return false; + + break; + + default: + + if ( MatchTag() ) + { + GetTag( &block ); + break; + } + + if ( MatchRandom() ) + { + GetRandom( &block ); + break; + } + + return Error("unknown parameter type"); + break; + } + } + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("set : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetLoop() + + Handles the loop() function. + +=================================================================================================== +*/ + +int CInterpreter::GetLoop( void ) +{ + CBlock block; + + block.Create( ID_LOOP ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( LookAhead( TK_CLOSED_PARENTHESIS ) ) + { + //-1 denotes an infinite loop + block.Write( TK_FLOAT, (float) -1); + } + else + { + if ( GetInteger( &block ) == false ) + return false; + } + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("GetLoop : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetPrint() + + Handles the print() function. + +=================================================================================================== +*/ + +int CInterpreter::GetPrint( void ) +{ + CBlock block; + + block.Create( ID_PRINT ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("print : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetUse() + + Handles the use() function. + +=================================================================================================== +*/ + +int CInterpreter::GetUse( void ) +{ + CBlock block; + + block.Create( ID_USE ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("use : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetFlush() + + Handles the flush() function. + +=================================================================================================== +*/ + +int CInterpreter::GetFlush( void ) +{ + CBlock block; + + block.Create( ID_FLUSH ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("flush : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetRun() + + Handles the run() function. + +=================================================================================================== +*/ + +int CInterpreter::GetRun( void ) +{ + CBlock block; + + block.Create( ID_RUN ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("run : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetKill() + + Handles the kill() function. + +=================================================================================================== +*/ + +int CInterpreter::GetKill( void ) +{ + CBlock block; + + block.Create( ID_KILL ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("kill : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetRemove() + + Handles the remove() function. + +=================================================================================================== +*/ + +int CInterpreter::GetRemove( void ) +{ + CBlock block; + + block.Create( ID_REMOVE ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("remove : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetRem() + + Handles the rem() function. + +=================================================================================================== +*/ + +// this is just so people can put comments in scripts in BehavEd and not have them lost as normal comments would be. +// +int CInterpreter::GetRem( void ) +{ + CBlock block; + + block.Create( ID_REM ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + // optional string? + + if (Match( TK_CLOSED_PARENTHESIS )) + return true; + + GetString( &block ); + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("rem : function only takes 1 optional parameter"); + + return true; +} + + +/* +=================================================================================================== + + GetCamera() + + Handles the camera() function. + +=================================================================================================== +*/ + +int CInterpreter::GetCamera( void ) +{ + CBlock block; + char typeName[MAX_STRING_SIZE]; + int type; + + block.Create( ID_CAMERA ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetType( (char *) typeName ) == false ) + return false; + + type = FindSymbol( typeName, m_typeKeywords); + + switch ( type ) + { + case TYPE_PAN: //PAN ( ANGLES, DURATION ) + + block.Write( TK_FLOAT, (float) type ); + + if ( GetVector( &block ) == false ) + return false; + + if ( GetVector( &block ) == false ) + return false; + + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TYPE_ZOOM: //ZOOM ( FOV, DURATION ) + + block.Write( TK_FLOAT, (float) type ); + + if ( GetFloat( &block ) == false ) + return false; + + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TYPE_MOVE: //MOVE ( ORIGIN, DURATION ) + + block.Write( TK_FLOAT, (float) type ); + + if ( GetVector( &block ) == false ) + return false; + + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TYPE_FADE: //FADE ( SOURCE(R,G,B,A), DEST(R,G,B,A), DURATION ) + + block.Write( TK_FLOAT, (float) type ); + + //Source color + if ( GetVector( &block ) == false ) + return false; + if ( GetFloat( &block ) == false ) + return false; + + //Dest color + if ( GetVector( &block ) == false ) + return false; + if ( GetFloat( &block ) == false ) + return false; + + //Duration + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TYPE_PATH: //PATH ( FILENAME ) + + block.Write( TK_FLOAT, (float) type ); + + //Filename + if ( GetString( &block ) == false ) + return false; + + break; + + case TYPE_ENABLE: + case TYPE_DISABLE: + + block.Write( TK_FLOAT, (float) type ); + break; + + case TYPE_SHAKE: //SHAKE ( INTENSITY, DURATION ) + + block.Write( TK_FLOAT, (float) type ); + + //Intensity + if ( GetFloat( &block ) == false ) + return false; + + //Duration + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TYPE_ROLL: //ROLL ( ANGLE, TIME ) + + block.Write( TK_FLOAT, (float) type ); + + //Angle + if ( GetFloat( &block ) == false ) + return false; + + //Time + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TYPE_TRACK: //TRACK ( TARGETNAME, SPEED, INITLERP ) + + block.Write( TK_FLOAT, (float) type ); + + //Target name + if ( GetString( &block ) == false ) + return false; + + //Speed + if ( GetFloat( &block ) == false ) + return false; + + //Init lerp + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TYPE_FOLLOW: //FOLLOW ( CAMERAGROUP, SPEED, INITLERP ) + + block.Write( TK_FLOAT, (float) type ); + + //Camera group + if ( GetString( &block ) == false ) + return false; + + //Speed + if ( GetFloat( &block ) == false ) + return false; + + //Init lerp + if ( GetFloat( &block ) == false ) + return false; + + break; + + case TYPE_DISTANCE: //DISTANCE ( DISTANCE, INITLERP ) + + block.Write( TK_FLOAT, (float) type ); + + //Distance + if ( GetFloat( &block ) == false ) + return false; + + //Init lerp + if ( GetFloat( &block ) == false ) + return false; + + break; + } + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("camera : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +------------------------- +GetDoWait +------------------------- +*/ + +int CInterpreter::GetDoWait( void ) +{ + CBlock block; + + //Write out the "do" portion + block.Create( ID_DO ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("do : too many parameters"); + + //Write out the accompanying "wait" + char *str = (char *) block.GetMemberData( 0 ); + + CBlock block2; + + block2.Create( ID_WAIT ); + + block2.Write( TK_STRING, (char *) str ); + + m_blockStream->WriteBlock( &block ); + m_blockStream->WriteBlock( &block2 ); + + return true; +} + +/* +------------------------- +GetSignal +------------------------- +*/ + +int CInterpreter::GetSignal( void ) +{ + CBlock block; + + block.Create( ID_SIGNAL ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("signal : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +------------------------- +GetSignal +------------------------- +*/ + +int CInterpreter::GetWaitSignal( void ) +{ + CBlock block; + + block.Create( ID_WAITSIGNAL ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("waitsignal : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +------------------------- +GetPlay +------------------------- +*/ + +int CInterpreter::GetPlay( void ) +{ + CBlock block; + + block.Create( ID_PLAY ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + if ( GetString( &block ) == false ) + return false; + + if ( GetString( &block ) == false ) + return false; + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("waitsignal : too many parameters"); + + m_blockStream->WriteBlock( &block ); + + return true; +} + +/* +=================================================================================================== + + GetTag() + + Handles the tag() identifier. + +=================================================================================================== +*/ + +//NOTENOTE: The tag's information is included as block members, not as a separate block. + +int CInterpreter::GetTag( CBlock *block ) +{ + char typeName[MAX_STRING_SIZE]; + int typeID; + + //Mark as a tag + block->Write( ID_TAG, (float) ID_TAG ); + + if (!Match( TK_OPEN_PARENTHESIS )) + return Error("syntax error : '(' not found"); + + //Get the tag name + if ( GetString( block ) == false ) + return false; + + //Get the lookup ID + GetType( (char *) typeName ); + + typeID = FindSymbol( (char *) typeName, m_typeKeywords); + + //Tags only contain origin and angles lookups + if ( (typeID != TYPE_ORIGIN) && (typeID != TYPE_ANGLES) ) + { + return Error("syntax error : 'tag' : %s is not a valid look up identifier", typeName ); + } + + block->Write( TK_FLOAT, (float) typeID ); + + if (!Match( TK_CLOSED_PARENTHESIS )) + return Error("tag : too many parameters"); + + return true; +} + +/* +=================================================================================================== + + Interpret function + +=================================================================================================== +*/ + +// note new return type, this now returns the bad block number, else 0 for success. +// +// I also return -ve block numbers for errors between blocks. Eg if you read 3 good blocks, then find an unexpected +// float in the script between blocks 3 & 4 then I return -3 to indicate the error is after that, but not block 4 +// +int CInterpreter::Interpret( CTokenizer *Tokenizer, CBlockStream *BlockStream, char *filename ) +{ + CBlock block; + CToken *token; + int type, blockLevel = 0, parenthesisLevel = 0; + + m_sCurrentFile = filename; // used during error reporting because you can't ask tokenizer for pushed streams + + m_tokenizer = Tokenizer; + m_blockStream = BlockStream; + + m_iCurrentLine = m_tokenizer->GetCurLine(); + token = m_tokenizer->GetToEndOfLine(TK_STRING); + m_sCurrentLine = token->GetStringValue(); + m_tokenizer->PutBackToken(token, false, NULL, true); + + m_iBadCBlockNumber = 0; + + while (m_tokenizer->GetRemainingSize() > 0) + { + token = m_tokenizer->GetToken( TKF_USES_EOL, 0 ); + type = token->GetType(); + + switch ( type ) + { + case TK_UNDEFINED: + token->Delete(); + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("%d : undefined token", type); + return m_iBadCBlockNumber; + break; + + case TK_EOF: + break; + + case TK_EOL: + // read the next line, then put it back + token->Delete(); + m_iCurrentLine = m_tokenizer->GetCurLine(); + token = m_tokenizer->GetToEndOfLine(TK_STRING); + m_sCurrentLine = token->GetStringValue(); + m_tokenizer->PutBackToken(token, false, NULL, true); + break; + + case TK_CHAR: + case TK_STRING: + token->Delete(); + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("syntax error : unexpected string"); + return m_iBadCBlockNumber; + break; + + case TK_INT: + token->Delete(); + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("syntax error : unexpected integer"); + return m_iBadCBlockNumber; + break; + + case TK_FLOAT: + token->Delete(); + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("syntax error : unexpected float"); + return m_iBadCBlockNumber; + break; + + case TK_IDENTIFIER: + m_iBadCBlockNumber++; + if (!GetID( (char *) token->GetStringValue() )) + { + token->Delete(); + return m_iBadCBlockNumber; + } + token->Delete(); + break; + + case TK_BLOCK_START: + token->Delete(); + if (parenthesisLevel) + { + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("syntax error : brace inside parenthesis"); + return m_iBadCBlockNumber; + } + + blockLevel++; + break; + + case TK_BLOCK_END: + token->Delete(); + if (parenthesisLevel) + { + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("syntax error : brace inside parenthesis"); + return m_iBadCBlockNumber; + } + + block.Create( ID_BLOCK_END ); + m_blockStream->WriteBlock( &block ); + block.Free(); + + blockLevel--; + break; + + case TK_OPEN_PARENTHESIS: + token->Delete(); + blockLevel++; + parenthesisLevel++; + break; + + case TK_CLOSED_PARENTHESIS: + token->Delete(); + blockLevel--; + parenthesisLevel--; + + if (parenthesisLevel<0) + { + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("syntax error : closed parenthesis with no opening match"); + return m_iBadCBlockNumber; + } + break; + + case TK_VECTOR_START: + token->Delete(); + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("syntax error : unexpected vector"); + return m_iBadCBlockNumber; + break; + + case TK_VECTOR_END: + token->Delete(); + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("syntax error : unexpected vector"); + return m_iBadCBlockNumber; + break; + } + } + + if ( blockLevel ) + { + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("error : open brace was not closed"); + return m_iBadCBlockNumber; + } + + if ( parenthesisLevel ) + { + m_iBadCBlockNumber = -m_iBadCBlockNumber; + Error("error: open parenthesis"); + return m_iBadCBlockNumber; + } + + //Release all the variable information, because it's already been written out + FreeVars(); + + m_iBadCBlockNumber = 0; + return m_iBadCBlockNumber; //true; +} + diff --git a/codemp/icarus/Memory.cpp b/codemp/icarus/Memory.cpp new file mode 100644 index 0000000..ef41be4 --- /dev/null +++ b/codemp/icarus/Memory.cpp @@ -0,0 +1,20 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "icarus.h" + +// leave these two as standard mallocs for the moment, there's something weird happening in ICARUS... +// +void *ICARUS_Malloc(int iSize) +{ + //return gi.Malloc(iSize, TAG_ICARUS); + //return malloc(iSize); + return Z_Malloc(iSize, TAG_ICARUS5, qfalse); +} + +void ICARUS_Free(void *pMem) +{ + //gi.Free(pMem); + //free(pMem); + Z_Free(pMem); +} diff --git a/codemp/icarus/Q3_Interface.cpp b/codemp/icarus/Q3_Interface.cpp new file mode 100644 index 0000000..afa2798 --- /dev/null +++ b/codemp/icarus/Q3_Interface.cpp @@ -0,0 +1,1013 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// ICARUS Engine Interface File +// +// This file is the only section of the ICARUS systems that +// is not directly portable from engine to engine. +// +// -- jweier + +#include "../game/g_public.h" +#include "../server/server.h" +#include "interface.h" +#include "GameInterface.h" +#include "Q3_Interface.h" +#include "Q3_Registers.h" + +#define stringIDExpand(str, strEnum) str, strEnum, ENUM2STRING(strEnum) +//#define stringIDExpand(str, strEnum) str,strEnum + +/* +stringID_table_t tagsTable [] = +{ +} +*/ + +//rwwFIXEFIXME: Put somewhere else +inline float Q_flrand(float min, float max) { + return ((rand() * (max - min)) / 32768.0F) + min; +} + +qboolean COM_ParseString( char **data, char **s ); + +//======================================================================= + +interface_export_t interface_export; + + +/* +============ +Q3_ReadScript + Description : Reads in a file and attaches the script directory properly + Return type : static int + Argument : const char *name + Argument : void **buf +============ +*/ +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 +} + +/* +============ +Q3_CenterPrint + Description : Prints a message in the center of the screen + Return type : static void + Argument : const char *format + Argument : ... +============ +*/ +static void Q3_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] == '!') + { + SV_SendServerCommand( NULL, "cp \"%s\"", (text+1) ); + return; + } + + SV_SendServerCommand( NULL, "cp \"%s\"", text ); + } + + Q3_DebugPrint( WL_VERBOSE, "%s\n", text); // Just a developers note + + 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. +------------------------- +*/ +#ifndef _XBOX // We borrow the one in g_ICARUScb.c +void Q3_TaskIDClear( int *taskID ) +{ + *taskID = -1; +} +#endif + +/* +------------------------- +qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ) +------------------------- +*/ +qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ) +{ + if ( !gSequencers[ent->s.number] || !gTaskManagers[ent->s.number] ) + { + 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( sharedEntity_t *ent, taskID_t taskType ) +------------------------- +*/ +void Q3_TaskIDComplete( sharedEntity_t *ent, taskID_t taskType ) +{ + if ( taskType < TID_CHAN_VOICE || taskType >= NUM_TIDS ) + { + return; + } + + if ( gTaskManagers[ent->s.number] && Q3_TaskIDPending( ent, taskType ) ) + {//Complete it + gTaskManagers[ent->s.number]->Completed( 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 +} + +/* +------------------------- +void Q3_SetTaskID( sharedEntity_t *ent, taskID_t taskType, int taskID ) +------------------------- +*/ + +void Q3_TaskIDSet( sharedEntity_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; +} + + +/* +============ +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_GetEntityByName + +Returns the sequencer of the entity by the given name +============= +*/ +static sharedEntity_t *Q3_GetEntityByName( const char *name ) +{ + sharedEntity_t *ent; + entlist_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( Q_strupr( (char *) temp ) ); + + if ( ei == ICARUS_EntList.end() ) + return NULL; + + ent = SV_GentityNum((*ei).second); + + return ent; + // this now returns the ent instead of the sequencer -- dmv 06/27/01 +// if (ent == NULL) +// return NULL; +// return gSequencers[ent->s.number]; +} + +/* +============= +Q3_GetTime + +Get the current game time +============= +*/ +static DWORD Q3_GetTime( void ) +{ + return svs.time; +} + +/* +============= +G_AddSexToMunroString + +Take any string, look for "kyle/" replace with "kyla/" 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_AddSexToMunroString ( char *string, qboolean qDoBoth ) +{ + char *start; + + if VALIDSTRING( string ) { + if ( g_sex->string[0] == 'f' ) { + start = strstr( string, "kyle/" ); + if ( start != NULL ) { + strncpy( start, "kyla", 5 ); + 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_PlaySound + +Plays a sound from an entity +============= +*/ +static int Q3_PlaySound( int taskID, int entID, const char *name, const char *channel ) +{ + T_G_ICARUS_PLAYSOUND *sharedMem = (T_G_ICARUS_PLAYSOUND *)sv.mSharedMemory; + + sharedMem->taskID = taskID; + sharedMem->entID = entID; + strcpy(sharedMem->name, name); + strcpy(sharedMem->channel, channel); + + return VM_Call(gvm, GAME_ICARUS_PLAYSOUND); +} + + +/* +============ +Q3_SetVar + Description : + Return type : static void + Argument : int taskID + Argument : int entID + Argument : const char *type_name + Argument : const char *data +============ +*/ +void Q3_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; + } + + Q3_DebugPrint( WL_ERROR, "%s variable or field not found!\n", type_name ); +} + +/* +============ +Q3_Set + Description : + Return type : void + Argument : int taskID + Argument : int entID + Argument : const char *type_name + Argument : const char *data +============ +*/ +static void Q3_Set( int taskID, int entID, const char *type_name, const char *data ) +{ + T_G_ICARUS_SET *sharedMem = (T_G_ICARUS_SET *)sv.mSharedMemory; + + sharedMem->taskID = taskID; + sharedMem->entID = entID; + strcpy(sharedMem->type_name, type_name); + strcpy(sharedMem->data, data); + + if (VM_Call(gvm, GAME_ICARUS_SET)) + { + gTaskManagers[entID]->Completed( taskID ); + } +} + + +/* +============ +Q3_Evaluate + Description : + Return type : int + Argument : int p1Type + Argument : const char *p1 + Argument : int p2Type + Argument : const char *p2 + Argument : int operatorType +============ +*/ +static int Q3_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 ) + { + Q3_DebugPrint( WL_ERROR, "Q3_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: + Q3_DebugPrint( WL_WARNING, "Q3_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: + Q3_DebugPrint( WL_ERROR, "Q3_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: + Q3_DebugPrint( WL_ERROR, "Q3_Evaluate vector comparisons of type GREATER THAN cannot be performed!"); + return false; + break; + + case TK_STRING: + case TK_IDENTIFIER: + Q3_DebugPrint( WL_ERROR, "Q3_Evaluate string comparisons of type GREATER THAN cannot be performed!"); + return false; + break; + + default: + Q3_DebugPrint( WL_ERROR, "Q3_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: + Q3_DebugPrint( WL_ERROR, "Q3_Evaluate vector comparisons of type LESS THAN cannot be performed!"); + return false; + break; + + case TK_STRING: + case TK_IDENTIFIER: + Q3_DebugPrint( WL_ERROR, "Q3_Evaluate string comparisons of type LESS THAN cannot be performed!"); + return false; + break; + + default: + Q3_DebugPrint( WL_ERROR, "Q3_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: + Q3_DebugPrint( WL_ERROR, "Q3_Evaluate unknown type used!\n"); + return false; + } + + break; + + default: + Q3_DebugPrint( WL_ERROR, "Q3_Evaluate unknown operator used!\n"); + break; + } + + return false; +} + +/* +------------------------- +Q3_CameraFade +------------------------- +*/ +static void Q3_CameraFade( float sr, float sg, float sb, float sa, float dr, float dg, float db, float da, float duration ) +{ + Q3_DebugPrint( WL_WARNING, "Q3_CameraFade: NOT SUPPORTED IN MP\n"); +} + +/* +------------------------- +Q3_CameraPath +------------------------- +*/ +static void Q3_CameraPath( const char *name ) +{ + Q3_DebugPrint( WL_WARNING, "Q3_CameraPath: NOT SUPPORTED IN MP\n"); +} + +/* +------------------------- +Q3_DebugPrint +------------------------- +*/ +void Q3_DebugPrint( int level, const char *format, ... ) +{ + //Don't print messages they don't want to see + //if ( g_ICARUSDebug->integer < level ) + if (!com_developer || !com_developer->integer) + 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 ( ( 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", SV_GentityNum(entNum)->script_targetname, entNum, buffer ); + break; + } + default: + case WL_VERBOSE: + Com_Printf ( S_COLOR_GREEN"INFO: %s", text ); + break; + } +} + +void CGCam_Anything( void ) +{ + Q3_DebugPrint( WL_WARNING, "Camera functions NOT SUPPORTED IN MP\n"); +} + +//These are useless for MP. Just taking it for now since I don't want to remove all calls to this in ICARUS. +int AppendToSaveGame(unsigned long chid, void *data, int length) +{ + return 1; +} + +// Changed by BTO (VV) - Visual C++ 7.1 doesn't allow default args on funcion pointers +int ReadFromSaveGame(unsigned long chid, void *pvAddress, int iLength /* , void **ppvAddressPtr = NULL */ ) +{ + return 1; +} + +void CGCam_Enable( void ) +{ + CGCam_Anything(); +} + +void CGCam_Disable( void ) +{ + CGCam_Anything(); +} + +void CGCam_Zoom( float FOV, float duration ) +{ + CGCam_Anything(); +} + +void CGCam_Pan( vec3_t dest, vec3_t panDirection, float duration ) +{ + CGCam_Anything(); +} + +void CGCam_Move( vec3_t dest, float duration ) +{ + CGCam_Anything(); +} + +#ifdef _XBOX +void CGCam_Shake( float intensity, int duration ); +#else +void CGCam_Shake( float intensity, int duration ) +{ + CGCam_Anything(); +} +#endif + +void CGCam_Follow( const char *cameraGroup, float speed, float initLerp ) +{ + CGCam_Anything(); +} + +void CGCam_Track( const char *trackName, float speed, float initLerp ) +{ + CGCam_Anything(); +} + +void CGCam_Distance( float distance, float initLerp ) +{ + CGCam_Anything(); +} + +void CGCam_Roll( float dest, float duration ) +{ + CGCam_Anything(); +} + +int ICARUS_LinkEntity( int entID, CSequencer *sequencer, CTaskManager *taskManager ); + +static DWORD Q3_GetTimeScale( void ) +{ + return com_timescale->value; +} + +static void Q3_Lerp2Pos( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ) +{ + T_G_ICARUS_LERP2POS *sharedMem = (T_G_ICARUS_LERP2POS *)sv.mSharedMemory; + + sharedMem->taskID = taskID; + sharedMem->entID = entID; + VectorCopy(origin, sharedMem->origin); + + if (angles) + { + VectorCopy(angles, sharedMem->angles); + sharedMem->nullAngles = qfalse; + } + else + { + sharedMem->nullAngles = qtrue; + } + sharedMem->duration = duration; + + VM_Call(gvm, GAME_ICARUS_LERP2POS); + //We do this in case the values are modified in the game. It would be expected by icarus that + //the values passed in here are modified equally. + VectorCopy(sharedMem->origin, origin); + + if (angles) + { + VectorCopy(sharedMem->angles, angles); + } +} + +static void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration ) +{ + T_G_ICARUS_LERP2ORIGIN *sharedMem = (T_G_ICARUS_LERP2ORIGIN *)sv.mSharedMemory; + + sharedMem->taskID = taskID; + sharedMem->entID = entID; + VectorCopy(origin, sharedMem->origin); + sharedMem->duration = duration; + + VM_Call(gvm, GAME_ICARUS_LERP2ORIGIN); + VectorCopy(sharedMem->origin, origin); +} + +static void Q3_Lerp2Angles( int taskID, int entID, vec3_t angles, float duration ) +{ + T_G_ICARUS_LERP2ANGLES *sharedMem = (T_G_ICARUS_LERP2ANGLES *)sv.mSharedMemory; + + sharedMem->taskID = taskID; + sharedMem->entID = entID; + VectorCopy(angles, sharedMem->angles); + sharedMem->duration = duration; + + VM_Call(gvm, GAME_ICARUS_LERP2ANGLES); + VectorCopy(sharedMem->angles, angles); +} + +static int Q3_GetTag( int entID, const char *name, int lookup, vec3_t info ) +{ + int r; + T_G_ICARUS_GETTAG *sharedMem = (T_G_ICARUS_GETTAG *)sv.mSharedMemory; + + sharedMem->entID = entID; + strcpy(sharedMem->name, name); + sharedMem->lookup = lookup; + VectorCopy(info, sharedMem->info); + + r = VM_Call(gvm, GAME_ICARUS_GETTAG); + VectorCopy(sharedMem->info, info); + return r; +} + +static void Q3_Lerp2Start( int entID, int taskID, float duration ) +{ + T_G_ICARUS_LERP2START *sharedMem = (T_G_ICARUS_LERP2START *)sv.mSharedMemory; + + sharedMem->taskID = taskID; + sharedMem->entID = entID; + sharedMem->duration = duration; + + VM_Call(gvm, GAME_ICARUS_LERP2START); +} + +static void Q3_Lerp2End( int entID, int taskID, float duration ) +{ + T_G_ICARUS_LERP2END *sharedMem = (T_G_ICARUS_LERP2END *)sv.mSharedMemory; + + sharedMem->taskID = taskID; + sharedMem->entID = entID; + sharedMem->duration = duration; + + VM_Call(gvm, GAME_ICARUS_LERP2END); +} + +static void Q3_Use( int entID, const char *target ) +{ + T_G_ICARUS_USE *sharedMem = (T_G_ICARUS_USE *)sv.mSharedMemory; + + sharedMem->entID = entID; + strcpy(sharedMem->target, target); + + VM_Call(gvm, GAME_ICARUS_USE); +} + +static void Q3_Kill( int entID, const char *name ) +{ + T_G_ICARUS_KILL *sharedMem = (T_G_ICARUS_KILL *)sv.mSharedMemory; + + sharedMem->entID = entID; + strcpy(sharedMem->name, name); + + VM_Call(gvm, GAME_ICARUS_KILL); +} + +static void Q3_Remove( int entID, const char *name ) +{ + T_G_ICARUS_REMOVE *sharedMem = (T_G_ICARUS_REMOVE *)sv.mSharedMemory; + + sharedMem->entID = entID; + strcpy(sharedMem->name, name); + + VM_Call(gvm, GAME_ICARUS_REMOVE); +} + +static void Q3_Play( int taskID, int entID, const char *type, const char *name ) +{ + T_G_ICARUS_PLAY *sharedMem = (T_G_ICARUS_PLAY *)sv.mSharedMemory; + + sharedMem->taskID = taskID; + sharedMem->entID = entID; + strcpy(sharedMem->type, type); + strcpy(sharedMem->name, name); + + VM_Call(gvm, GAME_ICARUS_PLAY); +} + +static int Q3_GetFloat( int entID, int type, const char *name, float *value ) +{ + int r; + T_G_ICARUS_GETFLOAT *sharedMem = (T_G_ICARUS_GETFLOAT *)sv.mSharedMemory; + + sharedMem->entID = entID; + sharedMem->type = type; + strcpy(sharedMem->name, name); + sharedMem->value = 0;//*value; + + r = VM_Call(gvm, GAME_ICARUS_GETFLOAT); + *value = sharedMem->value; + return r; +} + +static int Q3_GetVector( int entID, int type, const char *name, vec3_t value ) +{ + int r; + T_G_ICARUS_GETVECTOR *sharedMem = (T_G_ICARUS_GETVECTOR *)sv.mSharedMemory; + + sharedMem->entID = entID; + sharedMem->type = type; + strcpy(sharedMem->name, name); + VectorCopy(value, sharedMem->value); + + r = VM_Call(gvm, GAME_ICARUS_GETVECTOR); + VectorCopy(sharedMem->value, value); + return r; +} + +static int Q3_GetString( int entID, int type, const char *name, char **value ) +{ + int r; + T_G_ICARUS_GETSTRING *sharedMem = (T_G_ICARUS_GETSTRING *)sv.mSharedMemory; + + sharedMem->entID = entID; + sharedMem->type = type; + strcpy(sharedMem->name, name); + + r = VM_Call(gvm, GAME_ICARUS_GETSTRING); + //rww - careful with this, next time shared memory is altered this will get stomped + *value = &sharedMem->value[0]; + return r; +} + + +/* +============ +Interface_Init + Description : Inits the interface for the game + Return type : void + Argument : interface_export_t *pe +============ +*/ +void Interface_Init( interface_export_t *pe ) +{ + //TODO: This is where you link up all your functions to the engine + + //General + pe->I_LoadFile = Q3_ReadScript; + pe->I_CenterPrint = Q3_CenterPrint; + pe->I_DPrintf = Q3_DebugPrint; + pe->I_GetEntityByName = Q3_GetEntityByName; + pe->I_GetTime = Q3_GetTime; + pe->I_GetTimeScale = Q3_GetTimeScale; + pe->I_PlaySound = Q3_PlaySound; + pe->I_Lerp2Pos = Q3_Lerp2Pos; + pe->I_Lerp2Origin = Q3_Lerp2Origin; + pe->I_Lerp2Angles = Q3_Lerp2Angles; + pe->I_GetTag = Q3_GetTag; + pe->I_Lerp2Start = Q3_Lerp2Start; + pe->I_Lerp2End = Q3_Lerp2End; + pe->I_Use = Q3_Use; + pe->I_Kill = Q3_Kill; + pe->I_Remove = Q3_Remove; + pe->I_Set = Q3_Set; + pe->I_Random = Q_flrand; + pe->I_Play = Q3_Play; + + //Camera functions + pe->I_CameraEnable = CGCam_Enable; + pe->I_CameraDisable = CGCam_Disable; + pe->I_CameraZoom = CGCam_Zoom; + pe->I_CameraMove = CGCam_Move; + pe->I_CameraPan = CGCam_Pan; + pe->I_CameraRoll = CGCam_Roll; + pe->I_CameraTrack = CGCam_Track; + pe->I_CameraFollow = CGCam_Follow; + pe->I_CameraDistance = CGCam_Distance; + pe->I_CameraShake = CGCam_Shake; + pe->I_CameraFade = Q3_CameraFade; + pe->I_CameraPath = Q3_CameraPath; + + //Variable information + pe->I_GetFloat = Q3_GetFloat; + pe->I_GetVector = Q3_GetVector; + pe->I_GetString = Q3_GetString; + + pe->I_Evaluate = Q3_Evaluate; + + pe->I_DeclareVariable = Q3_DeclareVariable; + pe->I_FreeVariable = Q3_FreeVariable; + + //Save / Load functions + pe->I_WriteSaveData = AppendToSaveGame; + pe->I_ReadSaveData = ReadFromSaveGame; + pe->I_LinkEntity = ICARUS_LinkEntity; +} diff --git a/codemp/icarus/Q3_Interface.h b/codemp/icarus/Q3_Interface.h new file mode 100644 index 0000000..4174880 --- /dev/null +++ b/codemp/icarus/Q3_Interface.h @@ -0,0 +1,297 @@ +#ifndef __Q3_INTERFACE__ +#define __Q3_INTERFACE__ + +//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 + + //# #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_FULLNAME,//## %s="NULL" # This name will appear when ent is scanned by tricorder + 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, it cannot unfreeze itself, you must have some other entity unfreeze a frozen ent!!! + SET_ICARUS_UNFREEZE,//## %s="NULL" # Specify name of entity to unfreeze - !!!NOTE!!! since the ent is frozen, it cannot unfreeze itself, you must have some other entity unfreeze a frozen ent!!! + + SET_SCROLLTEXT, //## %s="" # key of text string to print + SET_LCARSTEXT, //## %s="" # key of text string to print in LCARS frame + + //# #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 + + //# #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 + + //# #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. + + //# #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_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_MISSION_STATUS_SCREEN,//## %t="BOOL_TYPES" # Display Mission Status screen before advancing to next level + 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 + + //# #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="FORCE_LEVELS" # Change force power level + + 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_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_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; + +#ifdef __cplusplus + +// 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[1024]; + +//General +extern void Q3_TaskIDClear( int *taskID ); +extern qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ); +extern void Q3_TaskIDComplete( sharedEntity_t *ent, taskID_t taskType ); +extern void Q3_DPrintf( const char *, ... ); + +extern void Q3_CameraRoll( float angle, float duration ); +extern void Q3_CameraFollow( const char *name, float speed, float initLerp ); +extern void Q3_CameraTrack( const char *name, float speed, float initLerp ); +extern void Q3_CameraDistance( float distance, float initLerp ); + +//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 ); + +extern void Q3_DeclareVariable ( int type, const char *name ); +extern void Q3_FreeVariable( const char *name ); + +extern void Q3_DebugPrint( int level, const char *format, ... ); +#endif //__cplusplus + +#endif //__Q3_INTERFACE__ diff --git a/codemp/icarus/Q3_Registers.cpp b/codemp/icarus/Q3_Registers.cpp new file mode 100644 index 0000000..207c359 --- /dev/null +++ b/codemp/icarus/Q3_Registers.cpp @@ -0,0 +1,429 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../game/g_public.h" +#include "Q3_Registers.h" + +extern void Q3_DebugPrint( int level, const char *format, ... ); + +varString_m varStrings; +varFloat_m varFloats; +varString_m varVectors; //Work around for vector types + +int numVariables = 0; + +/* +------------------------- +Q3_VariableDeclared +------------------------- +*/ + +int Q3_VariableDeclared( const char *name ) +{ + //Check the strings + varString_m::iterator vsi = varStrings.find( name ); + + if ( vsi != varStrings.end() ) + return VTYPE_STRING; + + //Check the floats + varFloat_m::iterator vfi = varFloats.find( name ); + + if ( vfi != varFloats.end() ) + return VTYPE_FLOAT; + + //Check the vectors + varString_m::iterator vvi = varVectors.find( name ); + + if ( vvi != varVectors.end() ) + return VTYPE_VECTOR; + + return VTYPE_NONE; +} + +/* +------------------------- +Q3_DeclareVariable +------------------------- +*/ + +void Q3_DeclareVariable( int type, const char *name ) +{ + //Cannot declare the same variable twice + if ( Q3_VariableDeclared( name ) != VTYPE_NONE ) + return; + + if ( numVariables > MAX_VARIABLES ) + { + Q3_DebugPrint( WL_ERROR, "too many variables already declared, maximum is %d\n", MAX_VARIABLES ); + return; + } + + switch( type ) + { + case TK_FLOAT: + varFloats[ name ] = 0.0f; + break; + + case TK_STRING: + varStrings[ name ] = "NULL"; + break; + + case TK_VECTOR: + varVectors[ name ] = "0.0 0.0 0.0"; + break; + + default: + Q3_DebugPrint( WL_ERROR, "unknown 'type' for declare() function!\n" ); + return; + break; + } + + numVariables++; +} + +/* +------------------------- +Q3_FreeVariable +------------------------- +*/ + +void Q3_FreeVariable( const char *name ) +{ + //Check the strings + varString_m::iterator vsi = varStrings.find( name ); + + if ( vsi != varStrings.end() ) + { + varStrings.erase( vsi ); + numVariables--; + return; + } + + //Check the floats + varFloat_m::iterator vfi = varFloats.find( name ); + + if ( vfi != varFloats.end() ) + { + varFloats.erase( vfi ); + numVariables--; + return; + } + + //Check the strings + varString_m::iterator vvi = varVectors.find( name ); + + if ( vvi != varVectors.end() ) + { + varVectors.erase( vvi ); + numVariables--; + return; + } +} + +/* +------------------------- +Q3_GetFloatVariable +------------------------- +*/ + +int Q3_GetFloatVariable( const char *name, float *value ) +{ + //Check the floats + varFloat_m::iterator vfi = varFloats.find( name ); + + if ( vfi != varFloats.end() ) + { + *value = (*vfi).second; + return true; + } + + return false; +} + +/* +------------------------- +Q3_GetStringVariable +------------------------- +*/ + +int Q3_GetStringVariable( const char *name, const char **value ) +{ + //Check the strings + varString_m::iterator vsi = varStrings.find( name ); + + if ( vsi != varStrings.end() ) + { + *value = (const char *) ((*vsi).second).c_str(); + return true; + } + + return false; +} + +/* +------------------------- +Q3_GetVectorVariable +------------------------- +*/ + +int Q3_GetVectorVariable( const char *name, vec3_t value ) +{ + //Check the strings + varString_m::iterator vvi = varVectors.find( name ); + + if ( vvi != varVectors.end() ) + { + const char *str = ((*vvi).second).c_str(); + + sscanf( str, "%f %f %f", &value[0], &value[1], &value[2] ); + return true; + } + + return false; +} + +/* +------------------------- +Q3_InitVariables +------------------------- +*/ + +void Q3_InitVariables( void ) +{ + varStrings.clear(); + varFloats.clear(); + varVectors.clear(); + + if ( numVariables > 0 ) + Q3_DebugPrint( WL_WARNING, "%d residual variables found!\n", numVariables ); + + numVariables = 0; +} + +/* +------------------------- +Q3_SetVariable_Float +------------------------- +*/ + +int Q3_SetFloatVariable( const char *name, float value ) +{ + //Check the floats + varFloat_m::iterator vfi = varFloats.find( name ); + + if ( vfi == varFloats.end() ) + return VTYPE_FLOAT; + + (*vfi).second = value; + + return true; +} + +/* +------------------------- +Q3_SetVariable_String +------------------------- +*/ + +int Q3_SetStringVariable( const char *name, const char *value ) +{ + //Check the strings + varString_m::iterator vsi = varStrings.find( name ); + + if ( vsi == varStrings.end() ) + return false; + + (*vsi).second = value; + + return true; +} + +/* +------------------------- +Q3_SetVariable_Vector +------------------------- +*/ + +int Q3_SetVectorVariable( const char *name, const char *value ) +{ + //Check the strings + varString_m::iterator vvi = varVectors.find( name ); + + if ( vvi == varVectors.end() ) + return false; + + (*vvi).second = value; + + return true; +} + +/* +------------------------- +Q3_VariableSaveFloats +------------------------- +*/ + +void Q3_VariableSaveFloats( varFloat_m &fmap ) +{ + return; + /* + 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 ) ); + } + */ +} + +/* +------------------------- +Q3_VariableSaveStrings +------------------------- +*/ + +void Q3_VariableSaveStrings( varString_m &smap ) +{ + return; + /* + 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 ); + } + */ +} + +/* +------------------------- +Q3_VariableSave +------------------------- +*/ + +int Q3_VariableSave( void ) +{ + Q3_VariableSaveFloats( varFloats ); + Q3_VariableSaveStrings( varStrings ); + Q3_VariableSaveStrings( varVectors); + + return qtrue; +} + +/* +------------------------- +Q3_VariableLoadFloats +------------------------- +*/ + +void Q3_VariableLoadFloats( varFloat_m &fmap ) +{ + return; + /* + 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 ) ); + + Q3_DeclareVariable( TK_FLOAT, (const char *) &tempBuffer ); + Q3_SetFloatVariable( (const char *) &tempBuffer, val ); + } + */ +} + +/* +------------------------- +Q3_VariableLoadStrings +------------------------- +*/ + +void Q3_VariableLoadStrings( int type, varString_m &fmap ) +{ + return; + /* + 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: + Q3_DeclareVariable( TK_STRING, (const char *) &tempBuffer ); + Q3_SetStringVariable( (const char *) &tempBuffer, (const char *) &tempBuffer2 ); + break; + + case TK_VECTOR: + Q3_DeclareVariable( TK_VECTOR, (const char *) &tempBuffer ); + Q3_SetVectorVariable( (const char *) &tempBuffer, (const char *) &tempBuffer2 ); + break; + } + } + */ +} + +/* +------------------------- +Q3_VariableLoad +------------------------- +*/ + +int Q3_VariableLoad( void ) +{ + Q3_InitVariables(); + + Q3_VariableLoadFloats( varFloats ); + Q3_VariableLoadStrings( TK_STRING, varStrings ); + Q3_VariableLoadStrings( TK_VECTOR, varVectors); + + return qfalse; +} \ No newline at end of file diff --git a/codemp/icarus/Q3_Registers.h b/codemp/icarus/Q3_Registers.h new file mode 100644 index 0000000..dcc94f7 --- /dev/null +++ b/codemp/icarus/Q3_Registers.h @@ -0,0 +1,36 @@ +#ifndef __Q3_REGISTERS__ +#define __Q3_REGISTERS__ + +enum +{ + VTYPE_NONE = 0, + VTYPE_FLOAT, + VTYPE_STRING, + VTYPE_VECTOR, +}; + +#ifdef __cplusplus + +#define MAX_VARIABLES 32 + +typedef map < string, string > varString_m; +typedef map < string, float > varFloat_m; + +extern varString_m varStrings; +extern varFloat_m varFloats; +extern varString_m varVectors; + +extern void Q3_InitVariables( void ); +extern void Q3_DeclareVariable( int type, const char *name ); +extern void Q3_FreeVariable( const char *name ); +extern int Q3_GetStringVariable( const char *name, const char **value ); +extern int Q3_GetFloatVariable( const char *name, float *value ); +extern int Q3_GetVectorVariable( const char *name, vec3_t value ); +extern int Q3_VariableDeclared( const char *name ); +extern int Q3_SetFloatVariable( const char *name, float value ); +extern int Q3_SetStringVariable( const char *name, const char *value ); +extern int Q3_SetVectorVariable( const char *name, const char *value ); + +#endif //__cplusplus + +#endif //__Q3_REGISTERS__ diff --git a/codemp/icarus/Sequence.cpp b/codemp/icarus/Sequence.cpp new file mode 100644 index 0000000..6331aea --- /dev/null +++ b/codemp/icarus/Sequence.cpp @@ -0,0 +1,559 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// Script Command Sequences +// +// -- jweier + +// this include must remain at the top of every Icarus CPP file +#include "icarus.h" + +#include + +CSequence::CSequence( void ) +{ + m_numCommands = 0; + m_flags = 0; + m_iterations = 1; + + m_parent = NULL; + m_return = NULL; +} + +CSequence::~CSequence( void ) +{ + Delete(); +} + +/* +------------------------- +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( void ) +{ + 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(); + + //Clear all held commands + for ( bi = m_commands.begin(); bi != m_commands.end(); bi++ ) + { + delete (*bi); //Free() handled internally + } + + m_commands.clear(); +} + + +/* +------------------------- +AddChild +------------------------- +*/ + +void CSequence::AddChild( CSequence *child ) +{ + assert( child ); + if ( child == NULL ) + return; + + m_children.insert( m_children.end(), child ); +} + +/* +------------------------- +RemoveChild +------------------------- +*/ + +void CSequence::RemoveChild( CSequence *child ) +{ + assert( child ); + if ( child == NULL ) + return; + + //Remove the child + m_children.remove( child ); +} + +/* +------------------------- +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; + } + + 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 ) + { + 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; +} + +/* +------------------------- +GetChild +------------------------- +*/ + +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 ) +{ + unsigned char flags; + int numMembers, bID, size; + CBlockMember *bm; + + //Save out the block ID + bID = block->GetBlockID(); + (m_owner->GetInterface())->I_WriteSaveData( 'BLID', &bID, sizeof ( bID ) ); + + //Save out the block's flags + flags = block->GetFlags(); + (m_owner->GetInterface())->I_WriteSaveData( 'BFLG', &flags, sizeof ( flags ) ); + + //Save out the number of members to read + numMembers = block->GetNumMembers(); + (m_owner->GetInterface())->I_WriteSaveData( 'BNUM', &numMembers, sizeof ( numMembers ) ); + + for ( int i = 0; i < numMembers; i++ ) + { + bm = block->GetMember( i ); + + //Save the block id + bID = bm->GetID(); + (m_owner->GetInterface())->I_WriteSaveData( 'BMID', &bID, sizeof ( bID ) ); + + //Save out the data size + size = bm->GetSize(); + (m_owner->GetInterface())->I_WriteSaveData( 'BSIZ', &size, sizeof( size ) ); + + //Save out the raw data + (m_owner->GetInterface())->I_WriteSaveData( 'BMEM', bm->GetData(), size ); + } + + return true; +} + +/* +------------------------- +Save +------------------------- +*/ + +int CSequence::Save( void ) +{ +#if 0 //piss off, stupid function + sequence_l::iterator ci; + block_l::iterator bi; + int id; + + //Save the parent (by GUID) + id = ( m_parent != NULL ) ? m_parent->GetID() : -1; + (m_owner->GetInterface())->I_WriteSaveData( 'SPID', &id, sizeof( id ) ); + + //Save the return (by GUID) + id = ( m_return != NULL ) ? m_return->GetID() : -1; + (m_owner->GetInterface())->I_WriteSaveData( 'SRID', &id, sizeof( id ) ); + + //Save the number of children +// (m_owner->GetInterface())->I_WriteSaveData( 'SNCH', &m_numChildren, sizeof( m_numChildren ) ); + + //Save out the children (only by GUID) + STL_ITERATE( ci, m_children ) + { + id = (*ci)->GetID(); + (m_owner->GetInterface())->I_WriteSaveData( 'SCHD', &id, sizeof( id ) ); + } + + //Save flags + (m_owner->GetInterface())->I_WriteSaveData( 'SFLG', &m_flags, sizeof( m_flags ) ); + + //Save iterations + (m_owner->GetInterface())->I_WriteSaveData( 'SITR', &m_iterations, sizeof( m_iterations ) ); + + //Save the number of commands + (m_owner->GetInterface())->I_WriteSaveData( 'SNMC', &m_numCommands, sizeof( m_numCommands ) ); + + //Save the commands + STL_ITERATE( bi, m_commands ) + { + SaveCommand( (*bi) ); + } + + return true; +#endif + return false; +} + +/* +------------------------- +Load +------------------------- +*/ + +int CSequence::Load( void ) +{ +#if 0 //piss off, stupid function + unsigned char flags; + CSequence *sequence; + CBlock *block; + int id, numMembers; + + int bID, bSize; + void *bData; + + //Get the parent sequence + (m_owner->GetInterface())->I_ReadSaveData( 'SPID', &id, sizeof( id ) ); + m_parent = ( id != -1 ) ? m_owner->GetSequence( id ) : NULL; + + //Get the return sequence + (m_owner->GetInterface())->I_ReadSaveData( 'SRID', &id, sizeof( id ) ); + m_return = ( id != -1 ) ? m_owner->GetSequence( id ) : NULL; + + //Get the number of children +// (m_owner->GetInterface())->I_ReadSaveData( 'SNCH', &m_numChildren, sizeof( m_numChildren ) ); + + //Reload all children + for ( int i = 0; i < m_numChildren; i++ ) + { + //Get the child sequence ID + (m_owner->GetInterface())->I_ReadSaveData( 'SCHD', &id, sizeof( id ) ); + + //Get the desired sequence + if ( ( sequence = m_owner->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 + (m_owner->GetInterface())->I_ReadSaveData( 'SFLG', &m_flags, sizeof( m_flags ) ); + + //Get the number of iterations + (m_owner->GetInterface())->I_ReadSaveData( 'SITR', &m_iterations, sizeof( m_iterations ) ); + + int numCommands; + + //Get the number of commands + (m_owner->GetInterface())->I_ReadSaveData( 'SNMC', &numCommands, sizeof( m_numCommands ) ); + + //Get all the commands + for ( i = 0; i < numCommands; i++ ) + { + //Get the block ID and create a new container + (m_owner->GetInterface())->I_ReadSaveData( 'BLID', &id, sizeof( id ) ); + block = new CBlock; + + block->Create( id ); + + //Read the block's flags + (m_owner->GetInterface())->I_ReadSaveData( 'BFLG', &flags, sizeof( flags ) ); + block->SetFlags( flags ); + + //Get the number of block members + (m_owner->GetInterface())->I_ReadSaveData( 'BNUM', &numMembers, sizeof( numMembers ) ); + + for ( int j = 0; j < numMembers; j++ ) + { + //Get the member ID + (m_owner->GetInterface())->I_ReadSaveData( 'BMID', &bID, sizeof( bID ) ); + + //Get the member size + (m_owner->GetInterface())->I_ReadSaveData( 'BSIZ', &bSize, sizeof( bSize ) ); + + //Get the member's data + if ( ( bData = ICARUS_Malloc( bSize ) ) == NULL ) + return false; + + //Get the actual raw data + (m_owner->GetInterface())->I_ReadSaveData( 'BMEM', bData, bSize ); + + //Write out the correct type + switch ( bID ) + { + case TK_INT: + { + assert(0); + int data = *(int *) bData; + block->Write( TK_FLOAT, (float) data ); + } + break; + + case TK_FLOAT: + block->Write( TK_FLOAT, *(float *) bData ); + break; + + case TK_STRING: + case TK_IDENTIFIER: + case TK_CHAR: + block->Write( TK_STRING, (char *) bData ); + break; + + case TK_VECTOR: + case TK_VECTOR_START: + block->Write( TK_VECTOR, *(vec3_t *) bData ); + break; + + case ID_TAG: + block->Write( ID_TAG, (float) ID_TAG ); + break; + + case ID_GET: + block->Write( ID_GET, (float) ID_GET ); + break; + + case ID_RANDOM: + block->Write( ID_RANDOM, *(float *) bData );//(float) ID_RANDOM ); + break; + + case TK_EQUALS: + case TK_GREATER_THAN: + case TK_LESS_THAN: + case TK_NOT: + block->Write( bID, 0 ); + break; + + default: + assert(0); + return false; + break; + } + + //Get rid of the temp memory + ICARUS_Free( bData ); + } + + //Save the block + //STL_INSERT( m_commands, block ); + PushCommand( block, PUSH_BACK ); + } + + return true; +#endif + return false; +} diff --git a/codemp/icarus/Sequencer.cpp b/codemp/icarus/Sequencer.cpp new file mode 100644 index 0000000..c97be55 --- /dev/null +++ b/codemp/icarus/Sequencer.cpp @@ -0,0 +1,2483 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// Script Command Sequencer +// +// -- jweier + +// this include must remain at the top of every Icarus CPP file +#include "icarus.h" +//#include "g_headers.h" + +#include "assert.h" + +// Sequencer + +CSequencer::CSequencer( void ) +{ + m_numCommands = 0; + + m_curStream = NULL; + m_curSequence = NULL; + + m_elseValid = 0; + m_elseOwner = NULL; + + m_curGroup = NULL; +} + +CSequencer::~CSequencer( void ) +{ + Free(); //Safe even if already freed +} + +/* +======================== +Create + +Static creation function +======================== +*/ + +CSequencer *CSequencer::Create ( void ) +{ + CSequencer *sequencer = new CSequencer; + + return sequencer; +} + +/* +======================== +Init + +Initializes the sequencer +======================== +*/ +int CSequencer::Init( int ownerID, interface_export_t *ie, CTaskManager *taskManager, ICARUS_Instance *iICARUS ) +{ + m_ownerID = ownerID; + m_owner = iICARUS; + m_taskManager = taskManager; + m_ie = ie; + + return SEQ_OK; +} + +/* +======================== +Free + +Releases all resources and re-inits the sequencer +======================== +*/ +int CSequencer::Free( void ) +{ + sequence_l::iterator sli; + + //Flush the sequences + for ( sli = m_sequences.begin(); sli != m_sequences.end(); sli++ ) + { + m_owner->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); + } + + return SEQ_OK; +} + +/* +------------------------- +Flush +------------------------- +*/ + +int CSequencer::Flush( CSequence *owner ) +{ + if ( owner == NULL ) + return SEQ_FAILED; + + Recall(); + + sequence_l::iterator sli; + + //Flush the sequences + for ( sli = m_sequences.begin(); sli != m_sequences.end(); ) + { + if ( ( (*sli) == owner ) || ( owner->HasChild( (*sli) ) ) || ( (*sli)->HasFlag( SQ_PENDING ) ) || ( (*sli)->HasFlag( SQ_TASK ) ) ) + { + sli++; + continue; + } + + //Delete it, and remove all references + RemoveSequence( (*sli) ); + m_owner->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( void ) +{ + CSequence *sequence = m_owner->GetSequence(); + + assert( sequence ); + if ( sequence == NULL ) + return NULL; + + //Add it to the list + m_sequences.insert( m_sequences.end(), sequence ); + + //FIXME: Temp fix + sequence->SetFlag( SQ_PENDING ); + + return sequence; +} + +CSequence *CSequencer::AddSequence( CSequence *parent, CSequence *returnSeq, int flags ) +{ + CSequence *sequence = m_owner->GetSequence(); + + assert( sequence ); + if ( sequence == NULL ) + return NULL; + + //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, PUSH_BACK ); +} + +/* +======================== +Run + +Runs a script +======================== +*/ +int CSequencer::Run( char *buffer, long size ) +{ + bstream_t *blockStream; + + Recall(); + + //Create a new stream + blockStream = AddStream(); + + //Open the stream as an IBI stream + if (!blockStream->stream->Open( buffer, size )) + { + m_ie->I_DPrintf( WL_ERROR, "invalid stream" ); + return SEQ_FAILED; + } + + CSequence *sequence = AddSequence( NULL, m_curSequence, SQ_COMMON ); + + // Interpret the command blocks and route them properly + if ( S_FAILED( Route( sequence, blockStream )) ) + { + //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 ) +{ + CSequence *new_sequence; + bstream_t *new_stream; + char *buffer; + char newname[ 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 = m_ie->I_LoadFile( newname, (void **) &buffer ); + + if ( buffer_size <= 0 ) + { + m_ie->I_DPrintf( WL_ERROR, "'%s' : could not open file\n", (char*) block->GetMemberData( 0 )); + 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 )) + { + m_ie->I_DPrintf( WL_ERROR, "invalid stream" ); + delete block; + block = NULL; + return SEQ_FAILED; + } + + //Create a new sequence + new_sequence = AddSequence( m_curSequence, m_curSequence, ( SQ_RUN | SQ_PENDING ) ); + + m_curSequence->AddChild( new_sequence ); + + // Interpret the command blocks and route them properly + if ( S_FAILED( Route( new_sequence, new_stream )) ) + { + //Error code is set inside of Route() + delete block; + block = NULL; + return SEQ_FAILED; + } + + m_curSequence = m_curSequence->GetReturn(); + + assert( m_curSequence ); + + block->Write( TK_FLOAT, (float) new_sequence->GetID() ); + PushCommand( block, PUSH_FRONT ); + + return SEQ_OK; +} + +/* +======================== +ParseIf + +Parses an if statement +======================== +*/ + +int CSequencer::ParseIf( CBlock *block, bstream_t *bstream ) +{ + CSequence *sequence; + + //Create the container sequence + sequence = AddSequence( m_curSequence, m_curSequence, SQ_CONDITIONAL ); + + assert( sequence ); + if ( sequence == NULL ) + { + m_ie->I_DPrintf( WL_ERROR, "ParseIf: failed to allocate container sequence" ); + delete block; + block = NULL; + return SEQ_FAILED; + } + + m_curSequence->AddChild( sequence ); + + //Add a unique conditional identifier to the block for reference later + block->Write( TK_FLOAT, (float) sequence->GetID() ); + + //Push this onto the stack to mark the conditional entrance + PushCommand( block, PUSH_FRONT ); + + //Recursively obtain the conditional body + Route( sequence, bstream ); + + m_elseValid = 2; + m_elseOwner = block; + + return SEQ_OK; +} + +/* +======================== +ParseElse + +Parses an else statement +======================== +*/ + +int CSequencer::ParseElse( CBlock *block, bstream_t *bstream ) +{ + //The else is not retained + delete block; + block = NULL; + + CSequence *sequence; + + //Create the container sequence + sequence = AddSequence( m_curSequence, m_curSequence, SQ_CONDITIONAL ); + + assert( sequence ); + if ( sequence == NULL ) + { + m_ie->I_DPrintf( 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 ) + { + m_ie->I_DPrintf( WL_ERROR, "Invalid 'else' found!\n" ); + return SEQ_FAILED; + } + + m_elseOwner->Write( TK_FLOAT, (float) sequence->GetID() ); + + m_elseOwner->SetFlag( BF_ELSE ); + + //Recursively obtain the conditional body + Route( sequence, bstream ); + + m_elseValid = 0; + m_elseOwner = NULL; + + return SEQ_OK; +} + +/* +======================== +ParseLoop + +Parses a loop command +======================== +*/ + +int CSequencer::ParseLoop( CBlock *block, bstream_t *bstream ) +{ + CSequence *sequence; + CBlockMember *bm; + float min, max; + int rIter; + int memberNum = 0; + + //Set the parent + sequence = AddSequence( m_curSequence, m_curSequence, ( SQ_LOOP | SQ_RETAIN ) ); + + assert( sequence ); + if ( sequence == NULL ) + { + m_ie->I_DPrintf( WL_ERROR, "ParseLoop : failed to allocate container sequence" ); + 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() == ID_RANDOM ) + { + //Parse out the random number + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + rIter = (int) m_ie->I_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( TK_FLOAT, (float) sequence->GetID() ); + + //Push this onto the stack to mark the loop entrance + PushCommand( block, PUSH_FRONT ); + + //Recursively obtain the loop + Route( sequence, bstream ); + + 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 ) +{ + CSequence *sequence = AddSequence(); + bstream_t new_stream; + + sequence->SetFlag( SQ_AFFECT | SQ_PENDING ); + + if ( retain ) + sequence->SetFlag( 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 ) ) + { + 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 ) +{ + CSequencer *stream_sequencer = NULL; + char *entname = NULL; + int ret; + sharedEntity_t *ent = 0; + + entname = (char*) block->GetMemberData( 0 ); + ent = m_ie->I_GetEntityByName( entname ); + + if( !ent ) // 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 TK_STRING: + case TK_IDENTIFIER: + case TK_CHAR: + p1 = (char *) bm->GetData(); + break; + + case 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 TK_STRING: + //only string is acceptable for affect, store result in p1 + if ( m_ie->I_GetString( m_ownerID, type, name, &p1 ) == false) + { + delete block; + block = NULL; + return false; + } + break; + default: + //FIXME: Make an enum id for the error... + m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on affect _1" ); + delete block; + block = NULL; + return false; + break; + } + + break; + } + + default: + //FIXME: Make an enum id for the error... + m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on affect _2" ); + delete block; + block = NULL; + return false; + break; + }//end id switch + + if(p1) + { + ent = m_ie->I_GetEntityByName( p1 ); + } + if(!ent) + { // a valid entity name was not returned from the get command + m_ie->I_DPrintf( WL_WARNING, "'%s' : invalid affect() target\n"); + } + + } // end if(!ent) + + if( ent ) + { + stream_sequencer = gSequencers[ent->s.number];//ent->sequencer; + } + + if (stream_sequencer == NULL) + { + m_ie->I_DPrintf( 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 = m_owner->GetSequence(); + Route( trashSeq, bstream ); + Recall(); + DestroySequence( trashSeq ); + m_curSequence = backSeq; + delete block; + block = NULL; + return SEQ_OK; + } + + if S_FAILED ( stream_sequencer->AddAffect( bstream, (int) m_curSequence->HasFlag( SQ_RETAIN ), &ret ) ) + { + 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( TK_FLOAT, (float) ret ); + + PushCommand( block, 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 ) +{ + CSequence *sequence; + CTaskGroup *group; + const char *taskName; + + //Setup the container sequence + sequence = AddSequence( m_curSequence, m_curSequence, SQ_TASK | SQ_RETAIN ); + 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 ); + + if ( group == NULL ) + { + m_ie->I_DPrintf( WL_ERROR, "error : unable to allocate a new task group" ); + 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 ); + delete block; + block = NULL; + + //Recursively obtain the loop + Route( sequence, bstream ); + + 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 ) +{ + 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 ); + + //TEMP: HACK! + if ( m_elseValid ) + m_elseValid--; + + switch( block->GetBlockID() ) + { + //Marks the end of a blocked section + case ID_BLOCK_END: + + //Save this as a pre-process marker + PushCommand( block, PUSH_FRONT ); + + if ( m_curSequence->HasFlag( SQ_RUN ) || m_curSequence->HasFlag( SQ_AFFECT ) ) + { + //Go back to the last stream + m_curStream = bstream->last; + } + + if ( m_curSequence->HasFlag( 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 ID_AFFECT: + + if S_FAILED( ParseAffect( block, bstream ) ) + return SEQ_FAILED; + + break; + + //Run pre-processor + case ID_RUN: + + if S_FAILED( ParseRun( block ) ) + return SEQ_FAILED; + + break; + + //Loop pre-processor + case ID_LOOP: + + if S_FAILED( ParseLoop( block, bstream ) ) + return SEQ_FAILED; + + break; + + //Conditional pre-processor + case ID_IF: + + if S_FAILED( ParseIf( block, bstream ) ) + return SEQ_FAILED; + + break; + + case ID_ELSE: + + //TODO: Emit warning + if ( m_elseValid == 0 ) + { + m_ie->I_DPrintf( WL_ERROR, "Invalid 'else' found!\n" ); + return SEQ_FAILED; + } + + if S_FAILED( ParseElse( block, bstream ) ) + return SEQ_FAILED; + + break; + + case ID_TASK: + + if S_FAILED( ParseTask( block, bstream ) ) + return SEQ_FAILED; + + break; + + //FIXME: For now this is to catch problems, but can ultimately be removed + case ID_WAIT: + case ID_PRINT: + case ID_SOUND: + case ID_MOVE: + case ID_ROTATE: + case ID_SET: + case ID_USE: + case ID_REMOVE: + case ID_KILL: + case ID_FLUSH: + case ID_CAMERA: + case ID_DO: + case ID_DECLARE: + case ID_FREE: + case ID_SIGNAL: + case ID_WAITSIGNAL: + case ID_PLAY: + + //Commands go directly into the sequence without pre-process + PushCommand( block, PUSH_FRONT ); + break; + + //Error + default: + + m_ie->I_DPrintf( 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( SQ_RUN ) ) + { + block = new CBlock; + block->Create( ID_BLOCK_END ); + PushCommand( block, 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( POP_BACK ) ); + } + + 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 ) +{ + CBlock *block = *command; + + if ( block == NULL ) + return; + + //Check for a run command + if ( block->GetBlockID() == ID_RUN ) + { + int id = (int) (*(float *) block->GetMemberData( 1 )); + + m_ie->I_DPrintf( WL_DEBUG, "%4d run( \"%s\" ); [%d]", m_ownerID, (char *) block->GetMemberData(0), m_ie->I_GetTime() ); + + if ( m_curSequence->HasFlag( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + + *command = NULL; + } + + m_curSequence = GetSequence( id ); + + //TODO: Emit warning + assert( m_curSequence ); + if ( m_curSequence == NULL ) + { + m_ie->I_DPrintf( WL_ERROR, "Unable to find 'run' sequence!\n" ); + *command = NULL; + return; + } + + if ( m_curSequence->GetNumCommands() > 0 ) + { + *command = PopCommand( POP_BACK ); + + Prep( command ); //Account for any other pre-processes + return; + } + + return; + } + + //Check for the end of a run + if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_RUN ) ) ) + { + if ( m_curSequence->HasFlag( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = ReturnSequence( m_curSequence ); + + if ( m_curSequence && m_curSequence->GetNumCommands() > 0 ) + { + *command = PopCommand( POP_BACK ); + + Prep( command ); //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 ) +{ + CBlockMember *bm; + char tempString1[128], tempString2[128]; + vector_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 TK_FLOAT: + sprintf( (char *) tempString1, "%.3f", *(float *) bm->GetData() ); + p1 = (char *) tempString1; + break; + + case 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 TK_STRING: + case TK_IDENTIFIER: + case TK_CHAR: + + p1 = (char *) bm->GetData(); + break; + + case 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 TK_FLOAT: + { + float fVal; + + if ( m_ie->I_GetFloat( m_ownerID, type, name, &fVal ) == false) + return false; + + sprintf( (char *) tempString1, "%.3f", fVal ); + p1 = (char *) tempString1; + } + + break; + + case TK_INT: + { + float fVal; + + if ( m_ie->I_GetFloat( m_ownerID, type, name, &fVal ) == false) + return false; + + sprintf( (char *) tempString1, "%d", (int) fVal ); + p1 = (char *) tempString1; + } + break; + + case TK_STRING: + + if ( m_ie->I_GetString( m_ownerID, type, name, &p1 ) == false) + return false; + + break; + + case TK_VECTOR: + { + vector_t vVal; + + if ( m_ie->I_GetVector( m_ownerID, type, name, vVal ) == false) + return false; + + sprintf( (char *) tempString1, "%.3f %.3f %.3f", vVal[0], vVal[1], vVal[2] ); + p1 = (char *) tempString1; + } + + break; + } + + break; + } + + case 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 = TK_FLOAT; + + sprintf( (char *) tempString1, "%.3f", m_ie->I_Random( min, max ) ); + p1 = (char *) tempString1; + } + + break; + + case ID_TAG: + { + char *name; + float type; + + name = (char *) block->GetMemberData( memberNum++ ); + type = *(float *) block->GetMemberData( memberNum++ ); + + t1 = TK_VECTOR; + + //TODO: Emit warning + if ( m_ie->I_GetTag( m_ownerID, name, (int) type, vec ) == false) + { + m_ie->I_DPrintf( 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... + m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on conditional" ); + return false; + break; + } + + // + // Get the comparison operator + // + + bm = block->GetMember( memberNum++ ); + id = bm->GetID(); + + switch ( id ) + { + case TK_EQUALS: + case TK_GREATER_THAN: + case TK_LESS_THAN: + case TK_NOT: + oper = id; + break; + + default: + m_ie->I_DPrintf( 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 TK_FLOAT: + sprintf( (char *) tempString2, "%.3f", *(float *) bm->GetData() ); + p2 = (char *) tempString2; + break; + + case 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 TK_STRING: + case TK_IDENTIFIER: + case TK_CHAR: + + p2 = (char *) bm->GetData(); + break; + + case 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 TK_FLOAT: + { + float fVal; + + if ( m_ie->I_GetFloat( m_ownerID, type, name, &fVal ) == false) + return false; + + sprintf( (char *) tempString2, "%.3f", fVal ); + p2 = (char *) tempString2; + } + + break; + + case TK_INT: + { + float fVal; + + if ( m_ie->I_GetFloat( m_ownerID, type, name, &fVal ) == false) + return false; + + sprintf( (char *) tempString2, "%d", (int) fVal ); + p2 = (char *) tempString2; + } + break; + + case TK_STRING: + + if ( m_ie->I_GetString( m_ownerID, type, name, &p2 ) == false) + return false; + + break; + + case TK_VECTOR: + { + vector_t vVal; + + if ( m_ie->I_GetVector( m_ownerID, type, name, vVal ) == false) + return false; + + sprintf( (char *) tempString2, "%.3f %.3f %.3f", vVal[0], vVal[1], vVal[2] ); + p2 = (char *) tempString2; + } + + break; + } + + break; + } + + case 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 = TK_FLOAT; + + sprintf( (char *) tempString2, "%.3f", m_ie->I_Random( min, max ) ); + p2 = (char *) tempString2; + } + + break; + + case ID_TAG: + + { + char *name; + float type; + + name = (char *) block->GetMemberData( memberNum++ ); + type = *(float *) block->GetMemberData( memberNum++ ); + + t2 = TK_VECTOR; + + //TODO: Emit warning + if ( m_ie->I_GetTag( m_ownerID, name, (int) type, vec ) == false) + { + m_ie->I_DPrintf( 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... + m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on conditional" ); + return false; + break; + } + + return m_ie->I_Evaluate( t1, p1, t2, p2, oper ); +} + +/* +======================== +CheckIf + +Checks for if statement pre-processing +======================== +*/ + +void CSequencer::CheckIf( CBlock **command ) +{ + CBlock *block = *command; + int successID, failureID; + CSequence *successSeq, *failureSeq; + + if ( block == NULL ) + return; + + if ( block->GetBlockID() == ID_IF ) + { + int ret = EvaluateConditional( block ); + + 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 ) + { + m_ie->I_DPrintf( 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( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = successSeq; + + //Recursively work out any other pre-processors + *command = PopCommand( POP_BACK ); + Prep( command ); + + 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 ) + { + m_ie->I_DPrintf( 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( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = failureSeq; + + //Recursively work out any other pre-processors + *command = PopCommand( POP_BACK ); + Prep( command ); + + return; + } + + //Only save the conditional statement if the calling sequence is retained + if ( m_curSequence->HasFlag( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + //Conditional failed, just move on to the next command + *command = PopCommand( POP_BACK ); + Prep( command ); + + return; + } + + if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_CONDITIONAL ) ) ) + { + assert( m_curSequence->GetReturn() ); + if ( m_curSequence->GetReturn() == NULL ) + { + *command = NULL; + return; + } + + //Check to retain it + if ( m_curSequence->GetParent()->HasFlag( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + 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( POP_BACK ); + Prep( command ); + } +} + +/* +======================== +CheckLoop + +Checks for loop command pre-processing +======================== +*/ + +void CSequencer::CheckLoop( CBlock **command ) +{ + 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() == ID_LOOP ) + { + //Get the loop ID + bm = block->GetMember( memberNum++ ); + + if ( bm->GetID() == ID_RANDOM ) + { + //Parse out the random number + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + iterations = (int) m_ie->I_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 ) + { + m_ie->I_DPrintf( 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( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = loop; + + //Recursively work out any other pre-processors + *command = PopCommand( POP_BACK ); + Prep( command ); + + return; + } + + //Check for the end of the loop + if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( 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, PUSH_FRONT ); + + *command = PopCommand( POP_BACK ); + Prep( command ); + + return; + } + else + { + assert( m_curSequence->GetReturn() ); + if ( m_curSequence->GetReturn() == NULL ) + { + *command = NULL; + return; + } + + //Check to retain it + if ( m_curSequence->GetParent()->HasFlag( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + 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( POP_BACK ); + Prep( command ); + } + } +} + +/* +======================== +CheckFlush + +Checks for flush command pre-processing +======================== +*/ + +void CSequencer::CheckFlush( CBlock **command ) +{ + sequence_l::iterator sli; + CBlock *block = *command; + + if ( block == NULL ) + return; + + if ( block->GetBlockID() == ID_FLUSH ) + { + //Flush the sequence + Flush( m_curSequence ); + + //Check to retain it + if ( m_curSequence->HasFlag( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + *command = PopCommand( POP_BACK ); + Prep( command ); + + return; + } +} + +/* +======================== +CheckAffect + +Checks for affect command pre-processing +======================== +*/ + +void CSequencer::CheckAffect( CBlock **command ) +{ + CBlock *block = *command; + sharedEntity_t *ent = NULL; + char *entname = NULL; + int memberNum = 0; + + if ( block == NULL ) + { + return; + } + + if ( block->GetBlockID() == ID_AFFECT ) + { + CSequencer *sequencer = NULL; + entname = (char*) block->GetMemberData( memberNum++ ); + ent = m_ie->I_GetEntityByName( entname ); + + if( !ent ) // 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 TK_STRING: + case TK_IDENTIFIER: + case TK_CHAR: + p1 = (char *) bm->GetData(); + break; + + case 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 TK_STRING: + //only string is acceptable for affect, store result in p1 + if ( m_ie->I_GetString( m_ownerID, type, name, &p1 ) == false) + { + return; + } + break; + default: + //FIXME: Make an enum id for the error... + m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on affect _1" ); + return; + break; + } + + break; + } + + default: + //FIXME: Make an enum id for the error... + m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on affect _2" ); + return; + break; + }//end id switch + + if(p1) + { + ent = m_ie->I_GetEntityByName( p1 ); + } + if(!ent) + { // a valid entity name was not returned from the get command + m_ie->I_DPrintf( WL_WARNING, "'%s' : invalid affect() target\n"); + } + + } // end if(!ent) + + if( ent ) + { + sequencer = gSequencers[ent->s.number];//ent->sequencer; + } + 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( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + //NOTENOTE: If this isn't found, continue on to the next command + if ( sequencer == NULL ) + { + *command = PopCommand( POP_BACK ); + Prep( command ); + return; + } + + sequencer->Affect( id, type ); + + *command = PopCommand( POP_BACK ); + Prep( command ); + if( ent ) + { // ents need to update upon being affected + //ent->taskManager->Update(); + gTaskManagers[ent->s.number]->Update(); + } + + return; + } + + if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_AFFECT ) ) ) + { + if ( m_curSequence->HasFlag( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + m_curSequence = ReturnSequence( m_curSequence ); + + if ( m_curSequence == NULL ) + { + *command = NULL; + return; + } + + *command = PopCommand( POP_BACK ); + Prep( command ); + if( ent ) + { // ents need to update upon being affected + //ent->taskManager->Update(); + gTaskManagers[ent->s.number]->Update(); + } + + } +} + +/* +------------------------- +CheckDo +------------------------- +*/ + +void CSequencer::CheckDo( CBlock **command ) +{ + CBlock *block = *command; + + if ( block == NULL ) + return; + + if ( block->GetBlockID() == ID_DO ) + { + //Get the sequence + const char *groupName = (const char *) block->GetMemberData( 0 ); + CTaskGroup *group = m_taskManager->GetTaskGroup( groupName ); + CSequence *sequence = GetTaskSequence( group ); + + //TODO: Emit warning + assert( group ); + if ( group == NULL ) + { + //TODO: Give name/number of entity trying to execute, too + m_ie->I_DPrintf( 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 + m_ie->I_DPrintf( 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( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + 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 ); + + //Recursively work out any other pre-processors + *command = PopCommand( POP_BACK ); + Prep( command ); + + return; + } + + if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_TASK ) ) ) + { + if ( m_curSequence->HasFlag( SQ_RETAIN ) ) + { + PushCommand( block, PUSH_FRONT ); + } + else + { + delete block; + block = NULL; + *command = NULL; + } + + m_taskManager->MarkTask( m_curGroup->GetGUID(), TASK_END ); + 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( POP_BACK ); + Prep( command ); + } +} + +/* +======================== +Prep + +Handles internal sequencer maintenance +======================== +*/ + +void CSequencer::Prep( CBlock **command ) +{ + //Check all pre-processes + CheckAffect( command ); + CheckFlush( command ); + CheckLoop( command ); + CheckRun( command ); + CheckIf( command ); + CheckDo( command ); +} + +/* +======================== +Prime + +Starts communication between the task manager and this sequencer +======================== +*/ + +int CSequencer::Prime( CTaskManager *taskManager, CBlock *command ) +{ + Prep( &command ); + + if ( command ) + { + taskManager->SetCommand( command, PUSH_BACK ); + } + + 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 ) +{ + CBlock *command; + + if (returnCode == TASK_RETURN_COMPLETE) + { + //There are no more pending commands + if ( m_curSequence == NULL ) + { + delete block; + block = NULL; + return SEQ_OK; + } + + //Check to retain the command + if ( m_curSequence->HasFlag( SQ_RETAIN ) ) //This isn't true for affect sequences...? + { + PushCommand( block, PUSH_FRONT ); + } + else + { + 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( POP_BACK ); + Prep( &command ); + + if ( command ) + taskManager->SetCommand( command, PUSH_FRONT ); + + return SEQ_OK; + } + + //FIXME: This could be more descriptive + m_ie->I_DPrintf( WL_ERROR, "command could not be called back\n" ); + assert(0); + + return SEQ_FAILED; +} + +/* +------------------------- +Recall +------------------------- +*/ + +int CSequencer::Recall( void ) +{ + CBlock *block = NULL; + + if (!m_taskManager) + { //uh.. ok. + assert(0); + return true; + } + + while ( ( block = m_taskManager->RecallTask() ) != NULL ) + { + if (m_curSequence) + { + PushCommand( block, PUSH_BACK ); + } + else + { + delete block; + block = NULL; + } + } + + return true; +} + +/* +------------------------- +Affect +------------------------- +*/ + +int CSequencer::Affect( int id, int type ) +{ + CSequence *sequence = GetSequence( id ); + + if ( sequence == NULL ) + { + return SEQ_FAILED; + } + + switch ( type ) + { + + case TYPE_FLUSH: + + //Get rid of all old code + Flush( sequence ); + + sequence->RemoveFlag( SQ_PENDING, true ); + + m_curSequence = sequence; + + Prime( m_taskManager, PopCommand( POP_BACK ) ); + + break; + + case TYPE_INSERT: + + Recall(); + + sequence->SetReturn( m_curSequence ); + + sequence->RemoveFlag( SQ_PENDING, true ); + + m_curSequence = sequence; + + Prime( m_taskManager, PopCommand( POP_BACK ) ); + + break; + + + default: + m_ie->I_DPrintf( 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 ) +{ + 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 ) + { + m_ie->I_DPrintf( 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 ) +{ + if ( !sequence ) + 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; + } + } + + 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 ) ); + } + else + break; + } + + m_owner->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( void ) +{ +#if 0 //asfsfasadf + sequence_l::iterator si; + taskSequence_m::iterator ti; + int numSequences = 0, id, numTasks; + + //Get the number of sequences to save out + numSequences = m_sequences.size(); + + //Save out the owner sequence + m_ie->I_WriteSaveData( 'SQRE', &m_ownerID, sizeof( m_ownerID ) ); + + //Write out the number of sequences we need to read + m_ie->I_WriteSaveData( 'SQR#', &numSequences, sizeof( numSequences ) ); + + //Second pass, save out all sequences, in order + STL_ITERATE( si, m_sequences ) + { + id = (*si)->GetID(); + m_ie->I_WriteSaveData( 'SQRI', &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(); + m_ie->I_WriteSaveData( 'SQT#', &numTasks, sizeof ( numTasks ) ); + + STL_ITERATE( ti, m_taskSequences ) + { + //Save the task group's ID + id = ((*ti).first)->GetGUID(); + m_ie->I_WriteSaveData( 'STID', &id, sizeof( id ) ); + + //Save the sequence's ID + id = ((*ti).second)->GetID(); + m_ie->I_WriteSaveData( 'SSID', &id, sizeof( id ) ); + } + + int curGroupID = ( m_curGroup == NULL ) ? -1 : m_curGroup->GetGUID(); + + m_ie->I_WriteSaveData( 'SQCT', &curGroupID, sizeof ( m_numCommands ) ); + + //Output the number of commands + m_ie->I_WriteSaveData( 'SQ#C', &m_numCommands, sizeof ( m_numCommands ) ); //FIXME: This can be reconstructed + + //Output the ID of the current sequence + id = ( m_curSequence != NULL ) ? m_curSequence->GetID() : -1; + m_ie->I_WriteSaveData( 'SQCS', &id, sizeof ( id ) ); + + return true; +#endif + return false; +} + +/* +------------------------- +Load +------------------------- +*/ + +int CSequencer::Load( void ) +{ +#if 0 //asfsfasadf + //Get the owner of this sequencer + m_ie->I_ReadSaveData( 'SQRE', &m_ownerID, sizeof( m_ownerID ) ); + + //Link the entity back to the sequencer + m_ie->I_LinkEntity( m_ownerID, this, m_taskManager ); + + CTaskGroup *taskGroup; + CSequence *seq; + int numSequences, seqID, taskID, numTasks; + + //Get the number of sequences to read + m_ie->I_ReadSaveData( 'SQR#', &numSequences, sizeof( numSequences ) ); + + //Read in all the sequences + for ( int i = 0; i < numSequences; i++ ) + { + m_ie->I_ReadSaveData( 'SQRI', &seqID, sizeof( seqID ) ); + + seq = m_owner->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(); + + //Get the number of tasks in the map + m_ie->I_ReadSaveData( 'SQT#', &numTasks, sizeof( numTasks ) ); + + //Read in, and reassociate the tasks to the sequences + for ( i = 0; i < numTasks; i++ ) + { + //Read in the task's ID + m_ie->I_ReadSaveData( 'STID', &taskID, sizeof( taskID ) ); + + //Read in the sequence's ID + m_ie->I_ReadSaveData( 'SSID', &seqID, sizeof( seqID ) ); + + taskGroup = m_taskManager->GetTaskGroup( taskID ); + + assert( taskGroup ); + + seq = m_owner->GetSequence( seqID ); + + assert( seq ); + + //Associate the values + m_taskSequences[ taskGroup ] = seq; + } + + int curGroupID; + + //Get the current task group + m_ie->I_ReadSaveData( 'SQCT', &curGroupID, sizeof( curGroupID ) ); + + m_curGroup = ( curGroupID == -1 ) ? NULL : m_taskManager->GetTaskGroup( curGroupID ); + + //Get the number of commands + m_ie->I_ReadSaveData( 'SQ#C', &m_numCommands, sizeof( m_numCommands ) ); + + //Get the current sequence + m_ie->I_ReadSaveData( 'SQCS', &seqID, sizeof( seqID ) ); + + m_curSequence = ( seqID != -1 ) ? m_owner->GetSequence( seqID ) : NULL; + + return true; +#endif + return false; +} diff --git a/codemp/icarus/TaskManager.cpp b/codemp/icarus/TaskManager.cpp new file mode 100644 index 0000000..9361135 --- /dev/null +++ b/codemp/icarus/TaskManager.cpp @@ -0,0 +1,1994 @@ +// Task Manager +// +// -- jweier + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// this include must remain at the top of every Icarus CPP file +#include "icarus.h" + + + +#include + +#define ICARUS_VALIDATE(a) if ( a == false ) return TASK_FAILED; + +#include "../server/server.h" + +/* +================================================= + +CTask + +================================================= +*/ + +CTask::CTask( void ) +{ +} + +CTask::~CTask( void ) +{ +} + +CTask *CTask::Create( int GUID, CBlock *block ) +{ + CTask *task = new CTask; + + //TODO: Emit warning + assert( task ); + 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 ) +{ +} + +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; + + //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 ) +{ + 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 ) + { + (m_owner->GetInterface())->I_DPrintf( 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 ) +{ + taskGroupName_m::iterator tgi; + + tgi = m_taskGroupNameMap.find( name ); + + if ( tgi == m_taskGroupNameMap.end() ) + { + (m_owner->GetInterface())->I_DPrintf( WL_WARNING, "Could not find task group \"%s\"\n", name ); + return NULL; + } + + return (*tgi).second; +} + +CTaskGroup *CTaskManager::GetTaskGroup( int id ) +{ + taskGroupID_m::iterator tgi; + + tgi = m_taskGroupIDMap.find( id ); + + if ( tgi == m_taskGroupIDMap.end() ) + { + (m_owner->GetInterface())->I_DPrintf( WL_WARNING, "Could not find task group \"%d\"\n", id ); + return NULL; + } + + return (*tgi).second; +} + +/* +------------------------- +Update +------------------------- +*/ + +int CTaskManager::Update( void ) +{ + sharedEntity_t *owner = SV_GentityNum(m_ownerID); + + if ( (owner->r.svFlags&SVF_ICARUS_FREEZE) ) + { + return TASK_FAILED; + } + m_count = 0; //Needed for runaway init + m_resident = true; + + int returnVal = Go(); + + m_resident = false; + + return returnVal; +} + +/* +------------------------- +IsRunning +------------------------- +*/ + +qboolean CTaskManager::IsRunning( void ) +{ + return (qboolean)( m_tasks.empty() == false ); +} +/* +------------------------- +Check +------------------------- +*/ + +inline bool CTaskManager::Check( int targetID, CBlock *block, int memberNum ) +{ + if ( (block->GetMember( memberNum ))->GetID() == targetID ) + return true; + + return false; +} + +/* +------------------------- +GetFloat +------------------------- +*/ + +int CTaskManager::GetFloat( int entID, CBlock *block, int &memberNum, float &value ) +{ + char *name; + int type; + + //See if this is a get() command replacement + if ( Check( 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 != TK_FLOAT ) + { + (m_owner->GetInterface())->I_DPrintf( WL_ERROR, "Get() call tried to return a non-FLOAT parameter!\n" ); + return false; + } + + return (m_owner->GetInterface())->I_GetFloat( entID, type, name, &value ); + } + + //Look for a random() inline call + if ( Check( ID_RANDOM, block, memberNum ) ) + { + float min, max; + + memberNum++; + + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + value = (m_owner->GetInterface())->I_Random( min, max ); + + return true; + } + + //Look for a tag() inline call + if ( Check( ID_TAG, block, memberNum ) ) + { + (m_owner->GetInterface())->I_DPrintf( 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() == TK_INT ) + { + value = (float) (*(int *) block->GetMemberData( memberNum++ )); + } + else if ( bm->GetID() == TK_FLOAT ) + { + value = *(float *) block->GetMemberData( memberNum++ ); + } + else + { + assert(0); + (m_owner->GetInterface())->I_DPrintf( WL_WARNING, "Unexpected value; expected type FLOAT\n" ); + return false; + } + + return true; +} + +/* +------------------------- +GetVector +------------------------- +*/ + +int CTaskManager::GetVector( int entID, CBlock *block, int &memberNum, vector_t &value ) +{ + char *name; + int type, i; + + //See if this is a get() command replacement + if ( Check( 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 != TK_VECTOR ) + { + (m_owner->GetInterface())->I_DPrintf( WL_ERROR, "Get() call tried to return a non-VECTOR parameter!\n" ); + } + + return (m_owner->GetInterface())->I_GetVector( entID, type, name, value ); + } + + //Look for a random() inline call + if ( Check( 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) (m_owner->GetInterface())->I_Random( min, max ); //FIXME: Just truncating it for now.. should be fine though + } + + return true; + } + + //Look for a tag() inline call + if ( Check( ID_TAG, block, memberNum ) ) + { + char *tagName; + float tagLookup; + + memberNum++; + ICARUS_VALIDATE ( Get( entID, block, memberNum, &tagName ) ); + ICARUS_VALIDATE ( GetFloat( entID, block, memberNum, tagLookup ) ); + + if ( (m_owner->GetInterface())->I_GetTag( entID, tagName, (int) tagLookup, value ) == false) + { + (m_owner->GetInterface())->I_DPrintf( WL_ERROR, "Unable to find tag \"%s\" for ent %i!\n", tagName, entID ); +// assert(0); + return TASK_FAILED; + } + + return true; + } + + //Check for a real vector here + type = (int) (*(float *) block->GetMemberData( memberNum )); + + if ( type != TK_VECTOR ) + { +// (m_owner->GetInterface())->I_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] ) == false ) + return false; + } + + return true; +} + +/* +------------------------- +Get +------------------------- +*/ + +int CTaskManager::Get( int entID, CBlock *block, int &memberNum, char **value ) +{ + static char tempBuffer[128]; //FIXME: EEEK! + vector_t vector; + char *name, *tagName; + float tagLookup; + int type; + + //Look for a get() inline call + if ( Check( 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 TK_STRING: + if ( ( m_owner->GetInterface())->I_GetString( entID, type, name, value ) == false ) + { + (m_owner->GetInterface())->I_DPrintf( WL_ERROR, "Get() parameter \"%s\" could not be found!\n", name ); + return false; + } + + return true; + break; + + case TK_FLOAT: + { + float temp; + + if ( (m_owner->GetInterface())->I_GetFloat( entID, type, name, &temp ) == false ) + { + (m_owner->GetInterface())->I_DPrintf( 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 TK_VECTOR: + { + vector_t vval; + + if ( (m_owner->GetInterface())->I_GetVector( entID, type, name, vval ) == false ) + { + (m_owner->GetInterface())->I_DPrintf( 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: + (m_owner->GetInterface())->I_DPrintf( WL_ERROR, "Get() call tried to return an unknown type!\n" ); + return false; + break; + } + } + + //Look for a random() inline call + if ( Check( ID_RANDOM, block, memberNum ) ) + { + float min, max, ret; + + memberNum++; + + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + ret = ( m_owner->GetInterface())->I_Random( min, max ); + + sprintf( (char *) tempBuffer, "%f", ret ); + *value = (char *) tempBuffer; + + return true; + } + + //Look for a tag() inline call + if ( Check( ID_TAG, block, memberNum ) ) + { + memberNum++; + ICARUS_VALIDATE ( Get( entID, block, memberNum, &tagName ) ); + ICARUS_VALIDATE ( GetFloat( entID, block, memberNum, tagLookup ) ); + + if ( ( m_owner->GetInterface())->I_GetTag( entID, tagName, (int) tagLookup, vector ) == false) + { + (m_owner->GetInterface())->I_DPrintf( 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() == TK_INT ) + { + float fval = (float) (*(int *) block->GetMemberData( memberNum++ )); + sprintf( (char *) tempBuffer, "%f", fval ); + *value = (char *) tempBuffer; + + return true; + } + else if ( bm->GetID() == TK_FLOAT ) + { + float fval = *(float *) block->GetMemberData( memberNum++ ); + sprintf( (char *) tempBuffer, "%f", fval ); + *value = (char *) tempBuffer; + + return true; + } + else if ( bm->GetID() == TK_VECTOR ) + { + vector_t vval; + + memberNum++; + + for ( int i = 0; i < 3; i++ ) + { + if ( GetFloat( entID, block, memberNum, vval[i] ) == false ) + return false; + + sprintf( (char *) tempBuffer, "%f %f %f", vval[0], vval[1], vval[2] ); + *value = (char *) tempBuffer; + } + + return true; + } + else if ( ( bm->GetID() == TK_STRING ) || ( bm->GetID() == TK_IDENTIFIER ) ) + { + *value = (char *) block->GetMemberData( memberNum++ ); + + return true; + } + + //TODO: Emit warning + assert( 0 ); + (m_owner->GetInterface())->I_DPrintf( WL_WARNING, "Unexpected value; expected type STRING\n" ); + + return false; +} + +/* +------------------------- +Go +------------------------- +*/ + +int CTaskManager::Go( void ) +{ + CTask *task = NULL; + bool completed = false; + + //Check for run away scripts + if ( m_count++ > RUNAWAY_LIMIT ) + { + assert(0); + (m_owner->GetInterface())->I_DPrintf( 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( POP_BACK ); + + assert( task ); + if ( task == NULL ) + { + (m_owner->GetInterface())->I_DPrintf( 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( ( m_owner->GetInterface())->I_GetTime() ); + + //Switch and call the proper function + switch( task->GetID() ) + { + case ID_WAIT: + + Wait( task, completed ); + + //Push it to consider it again on the next frame if not complete + if ( completed == false ) + { + PushTask( task, PUSH_BACK ); + return TASK_OK; + } + + Completed( task->GetGUID() ); + + break; + + case ID_WAITSIGNAL: + + WaitSignal( task, completed ); + + //Push it to consider it again on the next frame if not complete + if ( completed == false ) + { + PushTask( task, PUSH_BACK ); + return TASK_OK; + } + + Completed( task->GetGUID() ); + + break; + + case ID_PRINT: //print( STRING ) + Print( task ); + break; + + case ID_SOUND: //sound( name ) + Sound( task ); + break; + + case ID_MOVE: //move ( ORIGIN, ANGLES, DURATION ) + Move( task ); + break; + + case ID_ROTATE: //rotate( ANGLES, DURATION ) + Rotate( task ); + break; + + case ID_KILL: //kill( NAME ) + Kill( task ); + break; + + case ID_REMOVE: //remove( NAME ) + Remove( task ); + break; + + case ID_CAMERA: //camera( ? ) + Camera( task ); + break; + + case ID_SET: //set( NAME, ? ) + Set( task ); + break; + + case ID_USE: //use( NAME ) + Use( task ); + break; + + case ID_DECLARE://declare( TYPE, NAME ) + DeclareVariable( task ); + break; + + case ID_FREE: //free( NAME ) + FreeVariable( task ); + break; + + case ID_SIGNAL: //signal( NAME ) + Signal( task ); + break; + + case ID_PLAY: //play ( NAME ) + Play( task ); + break; + + default: + assert(0); + task->Free(); + (m_owner->GetInterface())->I_DPrintf( WL_ERROR, "Found unknown task type!\n" ); + return TASK_FAILED; + break; + } + + //Pump the sequencer for another task + CallbackCommand( task, TASK_RETURN_COMPLETE ); + + 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 ) +{ + 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 ) + { + (m_owner->GetInterface())->I_DPrintf( 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 ) +{ + CTaskGroup *group = GetTaskGroup( id ); + + 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 ) +{ + if ( m_owner->Callback( this, task->GetBlock(), returnCode ) == SEQ_OK ) + return Go( ); + + assert(0); + + (m_owner->GetInterface())->I_DPrintf( WL_ERROR, "Command callback failure!\n" ); + return TASK_FAILED; +} + +/* +------------------------- +RecallTask +------------------------- +*/ + +CBlock *CTaskManager::RecallTask( void ) +{ + CTask *task; + + task = PopTask( 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 == PUSH_FRONT) || (flag == PUSH_BACK) ); + + switch ( flag ) + { + case PUSH_FRONT: + m_tasks.insert(m_tasks.begin(), task); + + return TASK_OK; + break; + + case PUSH_BACK: + m_tasks.insert(m_tasks.end(), task); + + return TASK_OK; + break; + } + + //Invalid flag + return SEQ_FAILED; +} + +/* +------------------------- +PopTask +------------------------- +*/ + +CTask *CTaskManager::PopTask( int flag ) +{ + CTask *task; + + assert( (flag == POP_FRONT) || (flag == POP_BACK) ); + + if ( m_tasks.empty() ) + return NULL; + + switch ( flag ) + { + case POP_FRONT: + task = m_tasks.front(); + m_tasks.pop_front(); + + return task; + break; + + case 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( 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 ) +{ + 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() == TK_STRING ) + { + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + if ( task->GetTimeStamp() == (m_owner->GetInterface())->I_GetTime() ) + { + //Print out the debug info + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d wait(\"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + } + + CTaskGroup *group = GetTaskGroup( sVal ); + + if ( group == NULL ) + { + //TODO: Emit warning + completed = false; + return TASK_FAILED; + } + + completed = group->Complete(); + } + else //Otherwise it's a time completion wait + { + if ( Check( ID_RANDOM, block, memberNum ) ) + {//get it random only the first time + float min, max; + + dwtime = *(float *) block->GetMemberData( memberNum++ ); + if ( dwtime == Q3_INFINITE ) + {//we have not evaluated this random yet + min = *(float *) block->GetMemberData( memberNum++ ); + max = *(float *) block->GetMemberData( memberNum++ ); + + dwtime = (m_owner->GetInterface())->I_Random( min, max ); + + //store the result in the first member + bm->SetData( &dwtime, sizeof( dwtime ) ); + } + } + else + { + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, dwtime ) ); + } + + if ( task->GetTimeStamp() == (m_owner->GetInterface())->I_GetTime() ) + { + //Print out the debug info + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d wait( %d ); [%d]", m_ownerID, (int) dwtime, task->GetTimeStamp() ); + } + + if ( (task->GetTimeStamp() + dwtime) < ((m_owner->GetInterface())->I_GetTime()) ) + { + completed = true; + memberNum = 0; + if ( Check( ID_RANDOM, block, memberNum ) ) + {//set the data back to 0 so it will be re-randomized next time + dwtime = Q3_INFINITE; + bm->SetData( &dwtime, sizeof( dwtime ) ); + } + } + } + + return TASK_OK; +} + +/* +------------------------- +WaitSignal +------------------------- +*/ + +int CTaskManager::WaitSignal( CTask *task, bool &completed ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + completed = false; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + if ( task->GetTimeStamp() == (m_owner->GetInterface())->I_GetTime() ) + { + //Print out the debug info + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d waitsignal(\"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + } + + if ( (m_owner->GetOwner())->CheckSignal( sVal ) ) + { + completed = true; + (m_owner->GetOwner())->ClearSignal( sVal ); + } + + return TASK_OK; +} + +/* +------------------------- +Print +------------------------- +*/ + +int CTaskManager::Print( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d print(\"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + + (m_owner->GetInterface())->I_CenterPrint( sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Sound +------------------------- +*/ + +int CTaskManager::Sound( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal, *sVal2; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d sound(\"%s\", \"%s\"); [%d]", m_ownerID, sVal, sVal2, task->GetTimeStamp() ); + + //Only instantly complete if the user has requested it + if( (m_owner->GetInterface())->I_PlaySound( task->GetGUID(), m_ownerID, sVal2, sVal ) ) + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Rotate +------------------------- +*/ + +int CTaskManager::Rotate( CTask *task ) +{ + vector_t vector; + CBlock *block = task->GetBlock(); + char *tagName; + float tagLookup, duration; + int memberNum = 0; + + //Check for a tag reference + if ( Check( ID_TAG, block, memberNum ) ) + { + memberNum++; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &tagName ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, tagLookup ) ); + + if ( (m_owner->GetInterface())->I_GetTag( m_ownerID, tagName, (int) tagLookup, vector ) == false ) + { + //TODO: Emit warning + (m_owner->GetInterface())->I_DPrintf( 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 ) ); + } + + //Find the duration + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, duration ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d rotate( <%f,%f,%f>, %d); [%d]", m_ownerID, vector[0], vector[1], vector[2], (int) duration, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_Lerp2Angles( task->GetGUID(), m_ownerID, vector, duration ); + + return TASK_OK; +} + +/* +------------------------- +Remove +------------------------- +*/ + +int CTaskManager::Remove( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d remove(\"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_Remove( m_ownerID, sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Camera +------------------------- +*/ + +int CTaskManager::Camera( CTask *task ) +{ + interface_export_t *ie = ( m_owner->GetInterface() ); + CBlock *block = task->GetBlock(); + vector_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 ) ); + + switch ( (int) type ) + { + case TYPE_PAN: + + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector ) ); + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector2 ) ); + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + + (m_owner->GetInterface())->I_DPrintf( 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() ); + ie->I_CameraPan( vector, vector2, fVal ); + break; + + case TYPE_ZOOM: + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( ZOOM, %f, %f); [%d]", m_ownerID, fVal, fVal2, task->GetTimeStamp() ); + ie->I_CameraZoom( fVal, fVal2 ); + break; + + case TYPE_MOVE: + + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( MOVE, <%f %f %f>, %f); [%d]", m_ownerID, vector[0], vector[1], vector[2], fVal, task->GetTimeStamp() ); + ie->I_CameraMove( vector, fVal ); + break; + + case TYPE_ROLL: + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( ROLL, %f, %f); [%d]", m_ownerID, fVal, fVal2, task->GetTimeStamp() ); + ie->I_CameraRoll( fVal, fVal2 ); + + break; + + case TYPE_FOLLOW: + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( FOLLOW, \"%s\", %f, %f); [%d]", m_ownerID, sVal, fVal, fVal2, task->GetTimeStamp() ); + ie->I_CameraFollow( (const char *) sVal, fVal, fVal2 ); + + break; + + case TYPE_TRACK: + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( TRACK, \"%s\", %f, %f); [%d]", m_ownerID, sVal, fVal, fVal2, task->GetTimeStamp() ); + ie->I_CameraTrack( (const char *) sVal, fVal, fVal2 ); + break; + + case TYPE_DISTANCE: + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( DISTANCE, %f, %f); [%d]", m_ownerID, fVal, fVal2, task->GetTimeStamp() ); + ie->I_CameraDistance( fVal, fVal2 ); + break; + + case TYPE_FADE: + + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector2 ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2 ) ); + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal3 ) ); + + (m_owner->GetInterface())->I_DPrintf( 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() ); + ie->I_CameraFade( vector[0], vector[1], vector[2], fVal, vector2[0], vector2[1], vector2[2], fVal2, fVal3 ); + break; + + case TYPE_PATH: + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( PATH, \"%s\"); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + ie->I_CameraPath( sVal ); + break; + + case TYPE_ENABLE: + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( ENABLE ); [%d]", m_ownerID, task->GetTimeStamp() ); + ie->I_CameraEnable(); + break; + + case TYPE_DISABLE: + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( DISABLE ); [%d]", m_ownerID, task->GetTimeStamp() ); + ie->I_CameraDisable(); + break; + + case TYPE_SHAKE: + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d camera( SHAKE, %f, %f ); [%d]", m_ownerID, fVal, fVal2, task->GetTimeStamp() ); + ie->I_CameraShake( fVal, (int) fVal2 ); + break; + } + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Move +------------------------- +*/ + +int CTaskManager::Move( CTask *task ) +{ + vector_t vector, vector2; + CBlock *block = task->GetBlock(); + float duration; + int memberNum = 0; + + //Get the goal position + ICARUS_VALIDATE( GetVector( m_ownerID, block, memberNum, vector ) ); + + //Check for possible angles field + if ( GetVector( m_ownerID, block, memberNum, vector2 ) == false ) + { + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, duration ) ); + + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d move( <%f %f %f>, %f ); [%d]", m_ownerID, vector[0], vector[1], vector[2], duration, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_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 ) ); + + (m_owner->GetInterface())->I_DPrintf( 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() ); + (m_owner->GetInterface())->I_Lerp2Pos( task->GetGUID(), m_ownerID, vector, vector2, duration ); + + return TASK_OK; +} + +/* +------------------------- +Kill +------------------------- +*/ + +int CTaskManager::Kill( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d kill( \"%s\" ); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_Kill( m_ownerID, sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Set +------------------------- +*/ + +int CTaskManager::Set( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal, *sVal2; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d set( \"%s\", \"%s\" ); [%d]", m_ownerID, sVal, sVal2, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_Set( task->GetGUID(), m_ownerID, sVal, sVal2 ); + + return TASK_OK; +} + +/* +------------------------- +Use +------------------------- +*/ + +int CTaskManager::Use( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d use( \"%s\" ); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_Use( m_ownerID, sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +DeclareVariable +------------------------- +*/ + +int CTaskManager::DeclareVariable( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + float fVal; + + ICARUS_VALIDATE( GetFloat( m_ownerID, block, memberNum, fVal ) ); + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d declare( %d, \"%s\" ); [%d]", m_ownerID, (int) fVal, sVal, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_DeclareVariable( (int) fVal, sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; + +} + +/* +------------------------- +FreeVariable +------------------------- +*/ + +int CTaskManager::FreeVariable( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d free( \"%s\" ); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_FreeVariable( sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; + +} + +/* +------------------------- +Signal +------------------------- +*/ + +int CTaskManager::Signal( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d signal( \"%s\" ); [%d]", m_ownerID, sVal, task->GetTimeStamp() ); + m_owner->GetOwner()->Signal( (const char *) sVal ); + + Completed( task->GetGUID() ); + + return TASK_OK; +} + +/* +------------------------- +Play +------------------------- +*/ + +int CTaskManager::Play( CTask *task ) +{ + CBlock *block = task->GetBlock(); + char *sVal, *sVal2; + int memberNum = 0; + + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal ) ); + ICARUS_VALIDATE( Get( m_ownerID, block, memberNum, &sVal2 ) ); + + (m_owner->GetInterface())->I_DPrintf( WL_DEBUG, "%4d play( \"%s\", \"%s\" ); [%d]", m_ownerID, sVal, sVal2, task->GetTimeStamp() ); + (m_owner->GetInterface())->I_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 ) +{ + unsigned char flags; + int numMembers, bID, size; + CBlockMember *bm; + + //Save out the block ID + bID = block->GetBlockID(); + (m_owner->GetInterface())->I_WriteSaveData( 'BLID', &bID, sizeof ( bID ) ); + + //Save out the block's flags + flags = block->GetFlags(); + (m_owner->GetInterface())->I_WriteSaveData( 'BFLG', &flags, sizeof ( flags ) ); + + //Save out the number of members to read + numMembers = block->GetNumMembers(); + (m_owner->GetInterface())->I_WriteSaveData( 'BNUM', &numMembers, sizeof ( numMembers ) ); + + for ( int i = 0; i < numMembers; i++ ) + { + bm = block->GetMember( i ); + + //Save the block id + bID = bm->GetID(); + (m_owner->GetInterface())->I_WriteSaveData( 'BMID', &bID, sizeof ( bID ) ); + + //Save out the data size + size = bm->GetSize(); + (m_owner->GetInterface())->I_WriteSaveData( 'BSIZ', &size, sizeof( size ) ); + + //Save out the raw data + (m_owner->GetInterface())->I_WriteSaveData( 'BMEM', bm->GetData(), size ); + } + + return true; +} + +/* +------------------------- +Save +------------------------- +*/ + +void CTaskManager::Save( void ) +{ +#if 0 + CTaskGroup *taskGroup; + const char *name; + CBlock *block; + DWORD timeStamp; + bool completed; + int id, numCommands; + int numWritten; + + //Save the taskmanager's GUID + (m_owner->GetInterface())->I_WriteSaveData( 'TMID', &m_GUID, sizeof( m_GUID ) ); //FIXME: This can be reconstructed + + //Save out the number of tasks that will follow + int iNumTasks = m_tasks.size(); + (m_owner->GetInterface())->I_WriteSaveData( 'TSK#', &iNumTasks, sizeof(iNumTasks) ); + + //Save out all the tasks + tasks_l::iterator ti; + + STL_ITERATE( ti, m_tasks ) + { + //Save the GUID + id = (*ti)->GetGUID(); + (m_owner->GetInterface())->I_WriteSaveData( 'TKID', &id, sizeof ( id ) ); + + //Save the timeStamp (FIXME: Although, this is going to be worthless if time is not consistent...) + timeStamp = (*ti)->GetTimeStamp(); + (m_owner->GetInterface())->I_WriteSaveData( 'TKTS', &timeStamp, sizeof ( timeStamp ) ); + + //Save out the block + block = (*ti)->GetBlock(); + SaveCommand( block ); + } + + //Save out the number of task groups + int numTaskGroups = m_taskGroups.size(); + (m_owner->GetInterface())->I_WriteSaveData( 'TG#G', &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(); + (m_owner->GetInterface())->I_WriteSaveData( 'TKG#', &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(); + (m_owner->GetInterface())->I_WriteSaveData( 'TKGP', &id, sizeof( id ) ); + + //Save out the number of commands + numCommands = (*tgi)->m_completedTasks.size(); + (m_owner->GetInterface())->I_WriteSaveData( 'TGNC', &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; + (m_owner->GetInterface())->I_WriteSaveData( 'GMID', &id, sizeof( id ) ); + + //Write out the state of completion + completed = (*tci).second; + (m_owner->GetInterface())->I_WriteSaveData( 'GMDN', &completed, sizeof( completed ) ); + } + + //Save out the number of completed commands + id = (*tgi)->m_numCompleted; + (m_owner->GetInterface())->I_WriteSaveData( 'TGDN', &id, sizeof( id ) ); //FIXME: This can be reconstructed + 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(); + (m_owner->GetInterface())->I_WriteSaveData( 'TGCG', &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 + (m_owner->GetInterface())->I_WriteSaveData( 'TGNL', &length, sizeof ( length ) ); + + //Write out the string + (m_owner->GetInterface())->I_WriteSaveData( 'TGNS', (void *) name, length ); + + taskGroup = (*tmi).second; + + id = taskGroup->GetGUID(); + + //Write out the ID + (m_owner->GetInterface())->I_WriteSaveData( 'TGNI', &id, sizeof( id ) ); + numWritten++; + } + assert (numWritten == numTaskGroups); +#endif +} + +/* +------------------------- +Load +------------------------- +*/ + +void CTaskManager::Load( void ) +{ +#if 0 + unsigned char flags; + CTaskGroup *taskGroup; + CBlock *block; + CTask *task; + DWORD timeStamp; + bool completed; + void *bData; + int id, numTasks, numMembers; + int bID, bSize; + + //Get the GUID + (m_owner->GetInterface())->I_ReadSaveData( 'TMID', &m_GUID, sizeof( m_GUID ) ); + + //Get the number of tasks to follow + (m_owner->GetInterface())->I_ReadSaveData( 'TSK#', &numTasks, sizeof( numTasks ) ); + + //Reload all the tasks + for ( int i = 0; i < numTasks; i++ ) + { + task = new CTask; + + assert( task ); + + //Get the GUID + (m_owner->GetInterface())->I_ReadSaveData( 'TKID', &id, sizeof( id ) ); + task->SetGUID( id ); + + //Get the time stamp + (m_owner->GetInterface())->I_ReadSaveData( 'TKTS', &timeStamp, sizeof( timeStamp ) ); + task->SetTimeStamp( timeStamp ); + + // + // BLOCK LOADING + // + + //Get the block ID and create a new container + (m_owner->GetInterface())->I_ReadSaveData( 'BLID', &id, sizeof( id ) ); + block = new CBlock; + + block->Create( id ); + + //Read the block's flags + (m_owner->GetInterface())->I_ReadSaveData( 'BFLG', &flags, sizeof( flags ) ); + block->SetFlags( flags ); + + //Get the number of block members + (m_owner->GetInterface())->I_ReadSaveData( 'BNUM', &numMembers, sizeof( numMembers ) ); + + for ( int j = 0; j < numMembers; j++ ) + { + //Get the member ID + (m_owner->GetInterface())->I_ReadSaveData( 'BMID', &bID, sizeof( bID ) ); + + //Get the member size + (m_owner->GetInterface())->I_ReadSaveData( 'BSIZ', &bSize, sizeof( bSize ) ); + + //Get the member's data + if ( ( bData = ICARUS_Malloc( bSize ) ) == NULL ) + { + assert( 0 ); + return; + } + + //Get the actual raw data + (m_owner->GetInterface())->I_ReadSaveData( 'BMEM', bData, bSize ); + + //Write out the correct type + switch ( bID ) + { + case TK_FLOAT: + block->Write( TK_FLOAT, *(float *) bData ); + break; + + case TK_IDENTIFIER: + block->Write( TK_IDENTIFIER, (char *) bData ); + break; + + case TK_STRING: + block->Write( TK_STRING, (char *) bData ); + break; + + case TK_VECTOR: + block->Write( TK_VECTOR, *(vec3_t *) bData ); + break; + + case ID_RANDOM: + block->Write( ID_RANDOM, *(float *) bData );//ID_RANDOM ); + break; + + case ID_TAG: + block->Write( ID_TAG, (float) ID_TAG ); + break; + + case ID_GET: + block->Write( ID_GET, (float) ID_GET ); + break; + + default: + (m_owner->GetInterface())->I_DPrintf( WL_ERROR, "Invalid Block id %d\n", bID); + assert( 0 ); + break; + } + + //Get rid of the temp memory + ICARUS_Free( bData ); + } + + task->SetBlock( block ); + + STL_INSERT( m_tasks, task ); + } + + //Load the task groups + int numTaskGroups; + + (m_owner->GetInterface())->I_ReadSaveData( 'TG#G', &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 + (m_owner->GetInterface())->I_ReadSaveData( 'TKG#', &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] ); + assert( taskGroup ); + + //Load the parent ID + (m_owner->GetInterface())->I_ReadSaveData( 'TKGP', &id, sizeof( id ) ); + + if ( id != -1 ) + taskGroup->m_parent = ( GetTaskGroup( id ) != NULL ) ? GetTaskGroup( id ) : NULL; + + //Get the number of commands in this group + (m_owner->GetInterface())->I_ReadSaveData( 'TGNC', &numMembers, sizeof( numMembers ) ); + + //Get each command and its completion state + for ( int j = 0; j < numMembers; j++ ) + { + //Get the ID + (m_owner->GetInterface())->I_ReadSaveData( 'GMID', &id, sizeof( id ) ); + + //Write out the state of completion + (m_owner->GetInterface())->I_ReadSaveData( 'GMDN', &completed, sizeof( completed ) ); + + //Save it out + taskGroup->m_completedTasks[ id ] = completed; + } + + //Get the number of completed tasks + (m_owner->GetInterface())->I_ReadSaveData( 'TGDN', &taskGroup->m_numCompleted, sizeof( taskGroup->m_numCompleted ) ); + } + + //Reload the currently active group + int curGroupID; + + (m_owner->GetInterface())->I_ReadSaveData( 'TGCG', &curGroupID, sizeof( curGroupID ) ); + + //Reload the map entries + for ( i = 0; i < numTaskGroups; i++ ) + { + char name[1024]; + int length; + + //Get the size of the string + (m_owner->GetInterface())->I_ReadSaveData( 'TGNL', &length, sizeof( length ) ); + + //Get the string + (m_owner->GetInterface())->I_ReadSaveData( 'TGNS', &name, length ); + + //Get the id + (m_owner->GetInterface())->I_ReadSaveData( 'TGNI', &id, sizeof( id ) ); + + taskGroup = GetTaskGroup( id ); + assert( taskGroup ); + + m_taskGroupNameMap[ name ] = taskGroup; + m_taskGroupIDMap[ taskGroup->GetGUID() ] = taskGroup; + } + + m_curGroup = ( curGroupID == -1 ) ? NULL : m_taskGroupIDMap[curGroupID]; + + delete[] taskIDs; +#endif +} diff --git a/codemp/icarus/Tokenizer.cpp b/codemp/icarus/Tokenizer.cpp new file mode 100644 index 0000000..4b83279 --- /dev/null +++ b/codemp/icarus/Tokenizer.cpp @@ -0,0 +1,2837 @@ +// Tokenizer.cpp +#ifndef NOT_USING_MODULES +// !!! if you are not using modules, read BELOW !!! +#include "Module.h" // if you are not using modules, + // create an empty Module.h in your + // project -- use of modules allows + // the error handler to be overridden + // with a custom CErrHandler +#endif +#include "Tokenizer.h" + +#pragma warning(disable : 4100) //unreferenced formal parameter +#pragma warning(disable : 4127) //conditional expression is constant +#pragma warning(disable : 4189) //local variable is initialized but not referenced +#pragma warning(disable : 4244) //conversion from x to x, possible loss of data + +enum +{ + DIR_INCLUDE = TK_USERDEF, + DIR_IFDEF, + DIR_IFNDEF, + DIR_ENDIF, + DIR_ELSE, + DIR_DEFINE, + DIR_UNDEFINE, +}; + +keywordArray_t CTokenizer::directiveKeywords[] = +{ + "include", DIR_INCLUDE, + "ifdef", DIR_IFDEF, + "ifndef", DIR_IFNDEF, + "endif", DIR_ENDIF, + "else", DIR_ELSE, + "define", DIR_DEFINE, + "undefine", DIR_UNDEFINE, + "", TK_EOF, +}; + +keywordArray_t CTokenizer::errorMessages[] = +{ + "No Error", TKERR_NONE, + "Unknown Error", TKERR_UNKNOWN, + "Buffer creation failed", TKERR_BUFFERCREATE, + "Unrecognized symbol", TKERR_UNRECOGNIZEDSYMBOL, + "Duplicate symbol", TKERR_DUPLICATESYMBOL, + "String length exceeded", TKERR_STRINGLENGTHEXCEEDED, + "Identifier length exceeded", TKERR_IDENTIFIERLENGTHEXCEEDED, + "Expected integer", TKERR_EXPECTED_INTEGER, + "Expected identifier", TKERR_EXPECTED_IDENTIFIER, + "Expected string", TKERR_EXPECTED_STRING, + "Expected char", TKERR_EXPECTED_CHAR, + "Expected float", TKERR_EXPECTED_FLOAT, + "Unexpected token", TKERR_UNEXPECTED_TOKEN, + "Invalid directive", TKERR_INVALID_DIRECTIVE, + "Include file not found", TKERR_INCLUDE_FILE_NOTFOUND, + "Unmatched directive", TKERR_UNMATCHED_DIRECTIVE, + "", TKERR_USERERROR, +}; + +// +// CSymbol +// + +CSymbol::CSymbol() +{ +} + +CSymbol::~CSymbol() +{ +} + +CSymbol* CSymbol::Create(LPCTSTR symbolName) +{ + CSymbol* retval = new CSymbol(); + retval->Init(symbolName); + return retval; +} + +LPCTSTR CSymbol::GetName() +{ + if (m_symbolName == NULL) + { + return ""; + } + return m_symbolName; +} + +void CSymbol::Init(LPCTSTR symbolName) +{ + m_symbolName = (char*)malloc(strlen(symbolName) + 1); +// ASSERT(m_symbolName); + strcpy(m_symbolName, symbolName); +} + +void CSymbol::Delete() +{ + if (m_symbolName != NULL) + { + free(m_symbolName); + m_symbolName = NULL; + } + delete this; +} + +// +// CDirectiveSymbol +// + +CDirectiveSymbol::CDirectiveSymbol() +{ +} + +CDirectiveSymbol::~CDirectiveSymbol() +{ +} + +CDirectiveSymbol* CDirectiveSymbol::Create(LPCTSTR symbolName) +{ + CDirectiveSymbol* retval = new CDirectiveSymbol(); + retval->Init(symbolName); + return retval; +} + +void CDirectiveSymbol::Init(LPCTSTR symbolName) +{ + CSymbol::Init(symbolName); + m_value = NULL; +} + +void CDirectiveSymbol::Delete() +{ + if (m_value != NULL) + { + free(m_value); + m_value = NULL; + } + CSymbol::Delete(); +} + +void CDirectiveSymbol::SetValue(LPCTSTR value) +{ + if (m_value != NULL) + { + free(m_value); + } + m_value = (char*)malloc(strlen(value) + 1); + strcpy(m_value, value); +} + +LPCTSTR CDirectiveSymbol::GetValue() +{ + return m_value; +} + +// +// CIntSymbol +// + +CIntSymbol::CIntSymbol() +{ +} + +CIntSymbol* CIntSymbol::Create(LPCTSTR symbolName, int value) +{ + CIntSymbol* retval = new CIntSymbol(); + retval->Init(symbolName, value); + return retval; +} + +void CIntSymbol::Delete() +{ + CSymbol::Delete(); +} + +void CIntSymbol::Init(LPCTSTR symbolName, int value) +{ + CSymbol::Init(symbolName); + m_value = value; +} + +int CIntSymbol::GetValue() +{ + return m_value; +} + +// +// CSymbolTable +// + +CSymbolTable::CSymbolTable() +{ + Init(); +} + +CSymbolTable::~CSymbolTable() +{ +} + +CSymbolTable* CSymbolTable::Create() +{ + CSymbolTable* retval = new CSymbolTable(); + retval->Init(); + return retval; +} + +void CSymbolTable::Init() +{ +} + +void CSymbolTable::DiscardSymbols() +{ + for (symbolmap_t::iterator isymbol = m_symbols.begin(); isymbol != m_symbols.end(); isymbol++) + { + (*isymbol).second->Delete(); + } + m_symbols.erase(m_symbols.begin(), m_symbols.end()); +} + +void CSymbolTable::Delete() +{ + DiscardSymbols(); + delete this; +} + +bool CSymbolTable::AddSymbol(CSymbol* theSymbol) +{ + LPCTSTR name = theSymbol->GetName(); + + symbolmap_t::iterator iter = m_symbols.find(name); + if (iter != m_symbols.end()) + { + return false; + } + m_symbols.insert(symbolmap_t::value_type(name, theSymbol)); + return true; +} + +CSymbol* CSymbolTable::FindSymbol(LPCTSTR symbolName) +{ + symbolmap_t::iterator iter = m_symbols.find(symbolName); + if (iter != m_symbols.end()) + { + return (*iter).second; + } + return NULL; +} + +CSymbol* CSymbolTable::ExtractSymbol(LPCTSTR symbolName) +{ + symbolmap_t::iterator iter = m_symbols.find(symbolName); + if (iter != m_symbols.end()) + { + CSymbol* retval = (*iter).second; + m_symbols.erase(iter); + } + return NULL; +} + +void CSymbolTable::RemoveSymbol(LPCTSTR symbolName) +{ + m_symbols.erase(symbolName); +} + +// +// CParseStream +// + +CParseStream::CParseStream() +{ +} + +CParseStream::~CParseStream() +{ +} + +CParseStream* CParseStream::Create() +{ + return NULL; +} + +void CParseStream::Delete() +{ + delete this; +} + +bool CParseStream::Init() +{ + m_next = NULL; + + return true; +} + +bool CParseStream::NextChar(byte& theByte) +{ + return false; +} + +long CParseStream::GetRemainingSize() +{ + return 0; +} + +CParseStream* CParseStream::GetNext() +{ + return m_next; +} + +void CParseStream::SetNext(CParseStream* next) +{ + m_next = next; +} + +int CParseStream::GetCurLine() +{ + return 0; +} + +void CParseStream::GetCurFilename(char** theBuff) +{ + *theBuff = NULL; +} + +bool CParseStream::IsThisDefinition(void* theDefinition) +{ + return false; +} + +// +// CParsePutBack +// + +CParsePutBack::CParsePutBack() +{ +} + +CParsePutBack::~CParsePutBack() +{ +} + +CParsePutBack* CParsePutBack::Create(byte theByte, int curLine, LPCTSTR filename) +{ + CParsePutBack* curParsePutBack = new CParsePutBack(); + curParsePutBack->Init(theByte, curLine, filename); + return curParsePutBack; +} + +void CParsePutBack::Delete() +{ + if (m_curFile != NULL) + { + free(m_curFile); + m_curFile = NULL; + } + delete this; +} + +bool CParsePutBack::NextChar(byte& theByte) +{ + if (m_consumed) + { + return false; + } + theByte = m_byte; + m_consumed = true; + return true; +} + +void CParsePutBack::Init(byte theByte, int curLine, LPCTSTR filename) +{ + CParseStream::Init(); + m_consumed = false; + m_byte = theByte; + m_curLine = curLine; + if (filename != NULL) + { + m_curFile = (char*)malloc(strlen(filename) + 1); + strcpy(m_curFile, filename); + } + else + { + m_curFile = NULL; + } +} + +long CParsePutBack::GetRemainingSize() +{ + if (m_consumed) + { + return 0; + } + else + { + return 1; + } +} + +int CParsePutBack::GetCurLine() +{ + return m_curLine; +} + +void CParsePutBack::GetCurFilename(char** theBuff) +{ + if (m_curFile == NULL) + { + *theBuff = NULL; + return; + } + *theBuff = (char*)malloc(strlen(m_curFile) + 1); + strcpy(*theBuff, m_curFile); +} + +// +// CParseFile +// + +CParseFile::CParseFile() +{ +} + +CParseFile::~CParseFile() +{ +} + +CParseFile* CParseFile::Create() +{ + CParseFile* theParseFile = new CParseFile(); + + if ( !theParseFile->Init() ) + { + delete theParseFile; + return NULL; + } + + return theParseFile; +} + +CParseFile* CParseFile::Create(LPCTSTR filename, CTokenizer* tokenizer) +{ + CParseFile* theParseFile = new CParseFile(); + + if ( theParseFile->Init(filename, tokenizer) ) + return theParseFile; + + return NULL; +} + +void CParseFile::Delete() +{ + if (m_buff != NULL) + { + free(m_buff); + m_buff = NULL; + } + if (m_ownsFile && (m_fileHandle != NULL)) + { + CloseHandle(m_fileHandle); + m_fileHandle = NULL; + } + if (m_fileName != NULL) + { + free(m_fileName); + m_fileName = NULL; + } + delete this; +} + +bool CParseFile::Init() +{ + m_fileHandle = NULL; + m_buff = NULL; + m_ownsFile = false; + m_curByte = NULL; + m_curLine = 1; + m_fileName = NULL; + return CParseStream::Init(); +} + +DWORD CParseFile::GetFileSize() +{ + DWORD dwCur = SetFilePointer(m_fileHandle, 0L, NULL, FILE_CURRENT); + DWORD dwLen = SetFilePointer(m_fileHandle, 0, NULL, FILE_END); + SetFilePointer(m_fileHandle, dwCur, NULL, FILE_BEGIN); + return dwLen; +} + +void CParseFile::Read(void* buff, UINT buffsize) +{ + DWORD bytesRead; + ReadFile(m_fileHandle, buff, buffsize, &bytesRead, NULL); +} + +bool CParseFile::Init(LPCTSTR filename, CTokenizer* tokenizer) +{ + CParseStream::Init(); + m_fileName = (char*)malloc(strlen(filename) + 1); + strcpy(m_fileName, filename); + DWORD dwAccess = GENERIC_READ; + DWORD dwShareMode = FILE_SHARE_WRITE | FILE_SHARE_READ; + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = 0; + DWORD dwCreateFlag = OPEN_EXISTING; + + m_fileHandle = CreateFile(filename, dwAccess, dwShareMode, &sa, dwCreateFlag, FILE_ATTRIBUTE_NORMAL, NULL); + + if (m_fileHandle == (HANDLE)-1) + { + tokenizer->Error(TKERR_INCLUDE_FILE_NOTFOUND); + Init(); + + return false; + } + + m_filesize = GetFileSize(); + m_buff = (byte*)malloc(m_filesize); + if (m_buff == NULL) + { + tokenizer->Error(TKERR_BUFFERCREATE); + Init(); + return false; + } + Read(m_buff, m_filesize); + m_curByte = 0; + m_curPos = 1; + m_ownsFile = true; + m_curLine = 1; + + return true; +} + +long CParseFile::GetRemainingSize() +{ + return m_filesize - m_curByte; +} + +bool CParseFile::NextChar(byte& theByte) +{ + if (m_curByte < m_filesize) + { + if (m_buff[m_curByte] == '\n') + { + m_curLine += 1; + m_curPos = 1; + } + else + { + m_curPos++; + } + theByte = m_buff[m_curByte++]; + return true; + } + else + { + return false; + } +} + +int CParseFile::GetCurLine() +{ + return m_curLine; +} + +void CParseFile::GetCurFilename(char** theBuff) +{ + *theBuff = NULL; + if (m_fileName != NULL) + { + *theBuff = (char*)malloc(strlen(m_fileName) + 1); + strcpy(*theBuff, m_fileName); + } +} + +// +// CParseMemory +// + +CParseMemory::CParseMemory() +{ +} + +CParseMemory::~CParseMemory() +{ +} + +CParseMemory* CParseMemory::Create(byte* data, long datasize) +{ + CParseMemory* curParse = new CParseMemory(); + curParse->Init(data, datasize); + return curParse; +} + +void CParseMemory::Delete() +{ + delete this; +} + +bool CParseMemory::NextChar(byte& theByte) +{ + if (m_offset < m_datasize) + { + if (m_data[m_offset] == '\n') + { + m_curLine += 1; + m_curPos = 1; + } + else + { + m_curPos++; + } + theByte = m_data[m_offset++]; + return true; + } + else + { + return false; + } +} + +void CParseMemory::Init(byte* data, long datasize) +{ + m_data = data; + m_curLine = 1; + m_curPos = 1; + m_offset = 0; + m_datasize = datasize; +} + +long CParseMemory::GetRemainingSize() +{ + return m_datasize - m_offset; +} + +int CParseMemory::GetCurLine() +{ + return m_curLine; +} + +void CParseMemory::GetCurFilename(char** theBuff) +{ + *theBuff = NULL; +} + +// +// CParseBlock +// + +CParseBlock::CParseBlock() +{ +} + +CParseBlock::~CParseBlock() +{ +} + +CParseBlock* CParseBlock::Create(byte* data, long datasize) +{ + CParseBlock* curParse = new CParseBlock(); + curParse->Init(data, datasize); + return curParse; +} + +void CParseBlock::Delete() +{ + if (m_data != NULL) + { + free(m_data); + m_data = NULL; + } + delete this; +} + +void CParseBlock::Init(byte* data, long datasize) +{ + m_data = (byte*)malloc(datasize); + memcpy(m_data, data, datasize); + m_curLine = 1; + m_curPos = 1; + m_offset = 0; + m_datasize = datasize; +} + +// +// CParseToken +// + +CParseToken::CParseToken() +{ +} + +CParseToken::~CParseToken() +{ +} + +CParseToken* CParseToken::Create(CToken* token) +{ + CParseToken* curParse = new CParseToken(); + curParse->Init(token); + return curParse; +} + +void CParseToken::Delete() +{ + if (m_data != NULL) + { + free(m_data); + m_data = NULL; + } + delete this; +} + +bool CParseToken::NextChar(byte& theByte) +{ + if (m_offset < m_datasize) + { + if (m_data[m_offset] == '\n') + { + m_curLine += 1; + m_curPos = 1; + } + else + { + m_curPos++; + } + theByte = m_data[m_offset++]; + return true; + } + else + { + return false; + } +} + +void CParseToken::Init(CToken* token) +{ + LPCTSTR tokenString = token->GetStringValue(); + m_datasize = strlen(tokenString); + if (m_datasize > 0) + { + m_data = (byte*) malloc(m_datasize); + memcpy(m_data, tokenString, m_datasize); + } + else + { + m_data = NULL; + } + m_curLine = 1; + m_curPos = 1; + m_offset = 0; + token->Delete(); +} + +long CParseToken::GetRemainingSize() +{ + return m_datasize - m_offset; +} + +int CParseToken::GetCurLine() +{ + return m_curLine; +} + +void CParseToken::GetCurFilename(char** theBuff) +{ + *theBuff = NULL; +} + +// +// CParseDefine +// + +CParseDefine::CParseDefine() +{ +} + +CParseDefine::~CParseDefine() +{ +} + +CParseDefine* CParseDefine::Create(CDirectiveSymbol* definesymbol) +{ + CParseDefine* retval = new CParseDefine(); + retval->Init(definesymbol); + return retval; +} + +void CParseDefine::Delete() +{ + CParseMemory::Delete(); +} + +void CParseDefine::Init(CDirectiveSymbol* definesymbol) +{ + CParseMemory::Init((byte*)definesymbol->GetValue(), strlen(definesymbol->GetValue())); + m_defineSymbol = definesymbol; +} + +bool CParseDefine::IsThisDefinition(void* theDefinition) +{ + return (CDirectiveSymbol*)theDefinition == m_defineSymbol; +} + +// +// CToken +// + +CToken::CToken() +{ +} + +CToken::~CToken() +{ +} + +CToken* CToken::Create() +{ + CToken* theToken = new CToken(); + theToken->Init(); + return theToken; +} + +void CToken::Delete() +{ + if (m_string != NULL) + { + free(m_string); + m_string = NULL; + } + delete this; +} + +void CToken::Init() +{ + m_next = NULL; + m_string = NULL; +} + +void CToken::SetNext(CToken* theToken) +{ + m_next = theToken; +} + +CToken* CToken::GetNext() +{ + return m_next; +} + +int CToken::GetType() +{ + return TK_EOF; +} + +int CToken::GetIntValue() +{ + return 0; +} + +LPCTSTR CToken::GetStringValue() +{ + if (m_string == NULL) + { + return ""; + } + return m_string; +} + +float CToken::GetFloatValue() +{ + return 0.0; +} + +// +// CCharToken +// + +CCharToken::CCharToken() +{ +} + +CCharToken::~CCharToken() +{ +} + +CCharToken* CCharToken::Create(byte theByte) +{ + CCharToken* theToken = new CCharToken(); + theToken->Init(theByte); + return theToken; +} + +void CCharToken::Delete() +{ + CToken::Delete(); +} + +void CCharToken::Init(byte theByte) +{ + CToken::Init(); + char charString[10]; + switch(theByte) + { + case '\0': + strcpy(charString, "\\0"); + break; + case '\n': + strcpy(charString, "\\n"); + break; + case '\\': + strcpy(charString, "\\\\"); + break; + case '\'': + strcpy(charString, "\\'"); + break; + case '\?': + strcpy(charString, "\\?"); + break; + case '\a': + strcpy(charString, "\\a"); + break; + case '\b': + strcpy(charString, "\\b"); + break; + case '\f': + strcpy(charString, "\\f"); + break; + case '\r': + strcpy(charString, "\\r"); + break; + case '\t': + strcpy(charString, "\\t"); + break; + case '\v': + strcpy(charString, "\\v"); + break; + default: + charString[0] = (char)theByte; + charString[1] = '\0'; + break; + } + m_string = (char*)malloc(strlen(charString) + 1); + strcpy(m_string, charString); +} + +int CCharToken::GetType() +{ + return TK_CHAR; +} + +// +// CStringToken +// + +CStringToken::CStringToken() +{ +} + +CStringToken::~CStringToken() +{ +} + +CStringToken* CStringToken::Create(LPCTSTR theString) +{ + CStringToken* theToken = new CStringToken(); + theToken->Init(theString); + return theToken; +} + +void CStringToken::Delete() +{ + CToken::Delete(); +} + +void CStringToken::Init(LPCTSTR theString) +{ + CToken::Init(); + m_string = (char*)malloc(strlen(theString) + 1); +// ASSERT(m_string); + strcpy(m_string, theString); +} + +int CStringToken::GetType() +{ + return TK_STRING; +} + +// +// CIntToken +// + +CIntToken::CIntToken() +{ +} + +CIntToken::~CIntToken() +{ +} + +CIntToken* CIntToken::Create(long value) +{ + CIntToken* theToken = new CIntToken(); + theToken->Init(value); + return theToken; +} + +void CIntToken::Delete() +{ + CToken::Delete(); +} + +void CIntToken::Init(long value) +{ + CToken::Init(); + m_value = value; +} + +int CIntToken::GetType() +{ + return TK_INT; +} + +int CIntToken::GetIntValue() +{ + return m_value; +} + +float CIntToken::GetFloatValue() +{ + return (float)m_value; +} + +LPCTSTR CIntToken::GetStringValue() +{ + if (m_string != NULL) + { + free(m_string); + m_string = NULL; + } + char temp[128]; + sprintf(temp, "%d", m_value); + m_string = (char*)malloc(strlen(temp) + 1); + strcpy(m_string, temp); + return m_string; +} + +// +// CFloatToken +// + +CFloatToken::CFloatToken() +{ +} + +CFloatToken::~CFloatToken() +{ +} + +CFloatToken* CFloatToken::Create(float value) +{ + CFloatToken* theToken = new CFloatToken(); + theToken->Init(value); + return theToken; +} + +void CFloatToken::Delete() +{ + CToken::Delete(); +} + +void CFloatToken::Init(float value) +{ + CToken::Init(); + m_value = value; +} + +int CFloatToken::GetType() +{ + return TK_FLOAT; +} + +float CFloatToken::GetFloatValue() +{ + return m_value; +} + +LPCTSTR CFloatToken::GetStringValue() +{ + if (m_string != NULL) + { + free(m_string); + m_string = NULL; + } + char temp[128]; + sprintf(temp, "%g", m_value); + m_string = (char*)malloc(strlen(temp) + 1); + strcpy(m_string, temp); + return m_string; +} + +// +// CIdentifierToken +// + +CIdentifierToken::CIdentifierToken() +{ +} + +CIdentifierToken::~CIdentifierToken() +{ +} + +CIdentifierToken* CIdentifierToken::Create(LPCTSTR name) +{ + CIdentifierToken* theToken = new CIdentifierToken(); + theToken->Init(name); + return theToken; +} + +void CIdentifierToken::Delete() +{ + CToken::Delete(); +} + +void CIdentifierToken::Init(LPCTSTR name) +{ + CToken::Init(); + m_string = (char*)malloc(strlen(name) + 1); +// ASSERT(m_string); + strcpy(m_string, name); +} + +int CIdentifierToken::GetType() +{ + return TK_IDENTIFIER; +} + +// +// CCommentToken +// + +CCommentToken::CCommentToken() +{ +} + +CCommentToken::~CCommentToken() +{ +} + +CCommentToken* CCommentToken::Create(LPCTSTR name) +{ + CCommentToken* theToken = new CCommentToken(); + theToken->Init(name); + return theToken; +} + +void CCommentToken::Delete() +{ + CToken::Delete(); +} + +void CCommentToken::Init(LPCTSTR name) +{ + CToken::Init(); + m_string = (char*)malloc(strlen(name) + 1); +// ASSERT(m_string); + strcpy(m_string, name); +} + +int CCommentToken::GetType() +{ + return TK_COMMENT; +} + +// +// CUserToken +// + +CUserToken::CUserToken() +{ +} + +CUserToken::~CUserToken() +{ +} + +CUserToken* CUserToken::Create(int value, LPCTSTR string) +{ + CUserToken* theToken = new CUserToken(); + theToken->Init(value, string); + return theToken; +} + +void CUserToken::Delete() +{ + CToken::Delete(); +} + +void CUserToken::Init(int value, LPCTSTR string) +{ + CToken::Init(); + m_value = value; + m_string = (char*)malloc(strlen(string) + 1); + strcpy(m_string, string); +} + +int CUserToken::GetType() +{ + return m_value; +} + +// +// CUndefinedToken +// + +CUndefinedToken::CUndefinedToken() +{ +} + +CUndefinedToken::~CUndefinedToken() +{ +} + +CUndefinedToken* CUndefinedToken::Create(LPCTSTR string) +{ + CUndefinedToken* theToken = new CUndefinedToken(); + theToken->Init(string); + return theToken; +} + +void CUndefinedToken::Delete() +{ + CToken::Delete(); +} + +void CUndefinedToken::Init(LPCTSTR string) +{ + CToken::Init(); + m_string = (char*)malloc(strlen(string) + 1); + strcpy(m_string, string); +} + +int CUndefinedToken::GetType() +{ + return TK_UNDEFINED; +} + +// +// CTokenizerState +// + +CTokenizerState::CTokenizerState() +{ +} + +CTokenizerState::~CTokenizerState() +{ +} + +CTokenizerState* CTokenizerState::Create(bool skip) +{ + CTokenizerState* retval = new CTokenizerState(); + retval->Init(skip); + return retval; +} + +void CTokenizerState::Init(bool skip) +{ + m_next = NULL; + m_skip = skip; + m_elseHit = false; +} + +void CTokenizerState::Delete() +{ + delete this; +} + +CTokenizerState* CTokenizerState::GetNext() +{ + return m_next; +} + +bool CTokenizerState::ProcessElse() +{ + if (!m_elseHit) + { + m_elseHit = true; + m_skip = !m_skip; + } + return m_elseHit; +} + +void CTokenizerState::SetNext(CTokenizerState* next) +{ + m_next = next; +} + +bool CTokenizerState::Skipping() +{ + return m_skip; +} + +// +// CTokenizerHolderState +// + +CTokenizerHolderState::CTokenizerHolderState() +{ +} + +CTokenizerHolderState::~CTokenizerHolderState() +{ +} + +CTokenizerHolderState* CTokenizerHolderState::Create() +{ + CTokenizerHolderState* retval = new CTokenizerHolderState(); + retval->Init(); + return retval; +} + +void CTokenizerHolderState::Init() +{ + CTokenizerState::Init(true); +} + +void CTokenizerHolderState::Delete() +{ + delete this; +} + +bool CTokenizerHolderState::ProcessElse() +{ + if (!m_elseHit) + { + m_elseHit = true; + } + return m_elseHit; +} + +// +// CKeywordTable +// + +CKeywordTable::CKeywordTable(CTokenizer* tokenizer, keywordArray_t* keywords) +{ + m_tokenizer = tokenizer; + m_holdKeywords = tokenizer->SetKeywords(keywords); +} + +CKeywordTable::~CKeywordTable() +{ + m_tokenizer->SetKeywords(m_holdKeywords); +} + +// +// CTokenizer +// + +CTokenizer::CTokenizer() +{ +} + +CTokenizer::~CTokenizer() +{ +} + +CTokenizer* CTokenizer::Create(UINT dwFlags) +{ + CTokenizer* theTokenizer = new CTokenizer(); + theTokenizer->Init(dwFlags); + return theTokenizer; +} + +void CTokenizer::Delete() +{ + while (m_curParseStream != NULL) + { + CParseStream* curStream = m_curParseStream; + m_curParseStream = curStream->GetNext(); + curStream->Delete(); + } + if (m_symbolLookup != NULL) + { + m_symbolLookup->Delete(); + m_symbolLookup = NULL; + } + while (m_nextToken != NULL) + { + CToken* curToken = m_nextToken; + m_nextToken = curToken->GetNext(); + curToken->Delete(); + } + while (m_state != NULL) + { + Error(TKERR_UNMATCHED_DIRECTIVE); + CTokenizerState* curState = m_state; + m_state = curState->GetNext(); + curState->Delete(); + } + +/* if (m_lastErrMsg != NULL) + { + free(m_lastErrMsg); + m_lastErrMsg = NULL; + }*/ + delete this; +} + +void CTokenizer::Error(int theError) +{ + char errString[128]; + char lookupstring[128]; + int i = 0; + while ((errorMessages[i].m_tokenvalue != TKERR_USERERROR) && (errorMessages[i].m_tokenvalue != theError)) + { + i++; + } + if ((errorMessages[i].m_tokenvalue == TKERR_USERERROR) && (m_errors != NULL)) + { + i = 0; + while ((m_errors[i].m_tokenvalue != TK_EOF) && (m_errors[i].m_tokenvalue != theError)) + { + i++; + } + strcpy(lookupstring, m_errors[i].m_keyword); + } + else + { + strcpy(lookupstring, errorMessages[i].m_keyword); + } + sprintf(errString, "Error -- %d, %s", theError, lookupstring); + Error(errString, theError); +} + +void CTokenizer::Error(int theError, LPCTSTR errString) +{ + char errstring[128]; + char lookupstring[128]; + int i = 0; + while ((errorMessages[i].m_tokenvalue != TKERR_USERERROR) && (errorMessages[i].m_tokenvalue != theError)) + { + i++; + } + if ((errorMessages[i].m_tokenvalue == TKERR_USERERROR) && (m_errors != NULL)) + { + i = 0; + while ((m_errors[i].m_tokenvalue != TK_EOF) && (m_errors[i].m_tokenvalue != theError)) + { + i++; + } + strcpy(lookupstring, m_errors[i].m_keyword); + } + else + { + strcpy(lookupstring, errorMessages[i].m_keyword); + } + sprintf(errstring, "Error -- %d, %s - %s", theError, lookupstring, errString); + Error(errstring, theError); +} + +void CTokenizer::Error(LPCTSTR errString, int theError) +{ + if (m_errorProc != NULL) + { + m_errorProc(errString); + } +#ifdef USES_MODULES + else + { + ReportError(theError, errString); + } +#endif +} + +bool CTokenizer::AddParseFile(LPCTSTR filename) +{ + CParseStream* newStream = CParseFile::Create(filename, this); + + if ( newStream != NULL ) + { + newStream->SetNext(m_curParseStream); + m_curParseStream = newStream; + return true; + } + + return false; +} + +void CTokenizer::AddParseStream(byte* data, long datasize) +{ + CParseStream* newStream = CParseMemory::Create(data, datasize); + newStream->SetNext(m_curParseStream); + m_curParseStream = newStream; +} + +long CTokenizer::GetRemainingSize() +{ + long retval = 0; + CParseStream* curStream = m_curParseStream; + while (curStream != NULL) + { + retval += curStream->GetRemainingSize(); + curStream = curStream->GetNext(); + } + return retval; +} + +LPCTSTR CTokenizer::LookupToken(int tokenID, keywordArray_t* theTable) +{ + if (theTable == NULL) + { + theTable = m_keywords; + } + if (theTable == NULL) + { + return NULL; + } + + int i = 0; + while (theTable[i].m_tokenvalue != TK_EOF) + { + if (theTable[i].m_tokenvalue == tokenID) + { + return theTable[i].m_keyword; + } + i++; + } + return NULL; +} + +void CTokenizer::PutBackToken(CToken* theToken, bool commented, LPCTSTR addedChars, bool bIgnoreThisTokenType) +{ + if (commented) + { + CParseToken* newStream = CParseToken::Create(theToken); + newStream->SetNext(m_curParseStream); + m_curParseStream = newStream; + + if (addedChars != NULL) + { + CParsePutBack* spacer = CParsePutBack::Create(' ', 0, NULL); + spacer->SetNext(m_curParseStream); + m_curParseStream = spacer; + + CParseBlock* newBlock = CParseBlock::Create((byte*)addedChars, strlen(addedChars)); + newBlock->SetNext(m_curParseStream); + m_curParseStream = newBlock; + } + + char temp[] = "// * "; + CParseBlock* newBlock = CParseBlock::Create((byte*)temp, strlen(temp)); + newBlock->SetNext(m_curParseStream); + m_curParseStream = newBlock; + return; + } + + switch(theToken->GetType()) + { + case TK_INT: + case TK_EOF: + case TK_UNDEFINED: + case TK_FLOAT: + case TK_CHAR: + case TK_STRING: + case TK_EOL: + case TK_COMMENT: + if (!bIgnoreThisTokenType) + { + theToken->SetNext(m_nextToken); + m_nextToken = theToken; + break; + } + default: + CParseToken* newStream = CParseToken::Create(theToken); + newStream->SetNext(m_curParseStream); + m_curParseStream = newStream; + break; + } + + if (addedChars != NULL) + { + CParseBlock* newBlock = CParseBlock::Create((byte*)addedChars, strlen(addedChars)); + newBlock->SetNext(m_curParseStream); + m_curParseStream = newBlock; + } +} + +CToken* CTokenizer::GetToken(keywordArray_t* keywords, UINT onFlags, UINT offFlags) +{ + keywordArray_t* holdKeywords = SetKeywords(keywords); + CToken* retval = GetToken(onFlags, offFlags); + SetKeywords(holdKeywords); + return retval; +} + +CToken* CTokenizer::GetToken(UINT onFlags, UINT offFlags) +{ + UINT holdFlags = m_flags; + + m_flags |= onFlags; + m_flags &= (~offFlags); + CToken* theToken = NULL; + while (theToken == NULL) + { + theToken = FetchToken(); + if (theToken == NULL) + { + continue; + } + if (theToken->GetType() == TK_EOF) + { + break; + } + if (m_state != NULL) + { + if (m_state->Skipping()) + { + theToken->Delete(); + theToken = NULL; + } + } + } + + m_flags = holdFlags; + return theToken; +} + +CToken* CTokenizer::GetToEndOfLine(int tokenType) +{ + // update, if you just want the whole line returned as a string, then allow a much bigger size than + // the default string size of only 128 chars... + // + if (tokenType == TK_STRING) + { + #define iRETURN_STRING_SIZE 2048 + char theString[iRETURN_STRING_SIZE]; + theString[0] = ' '; + + for (int i = 1; i < iRETURN_STRING_SIZE; i++) + { + if (NextChar((byte&)theString[i])) + { + if (theString[i] != '\n') + { + continue; + } + PutBackChar(theString[i]); + } + theString[i] = '\0'; + + return CStringToken::Create(theString); + } + + // line would maks a string too big to fit in buffer... + // + Error(TKERR_STRINGLENGTHEXCEEDED); + } + else + { + char theString[MAX_IDENTIFIER_LENGTH]; + theString[0] = ' '; + while (theString[0] == ' ') + { + if (!NextChar((byte&)theString[0])) + { + return NULL; + } + } + for (int i = 1; i < MAX_IDENTIFIER_LENGTH; i++) + { + if (NextChar((byte&)theString[i])) + { + if (theString[i] != '\n') + { + continue; + } + PutBackChar(theString[i]); + } + theString[i] = '\0'; + switch(tokenType) + { + case TK_COMMENT: + return CCommentToken::Create(theString); + case TK_IDENTIFIER: + default: + return CIdentifierToken::Create(theString); + } + } + Error(TKERR_IDENTIFIERLENGTHEXCEEDED); + } + return NULL; +} + +void CTokenizer::SkipToLineEnd() +{ + byte theByte; + while(NextChar(theByte)) + { + if (theByte == '\n') + { + break; + } + } +} + +CToken* CTokenizer::FetchToken() +{ + if (m_nextToken != NULL) + { + CToken* curToken = m_nextToken; + m_nextToken = curToken->GetNext(); + curToken->SetNext(NULL); + return curToken; + } + byte theByte; + CToken* theToken = NULL; + + while (true) + { + if (!NextChar(theByte)) + { + return CToken::Create(); + } + if (theByte <= ' ') + { + if ((theByte == '\n') && ((TKF_USES_EOL & m_flags) != 0)) + { + return CUserToken::Create(TK_EOL, "-EOLN-"); + } + continue; + } + switch(theByte) + { + case '#': + if ((m_flags & TKF_IGNOREDIRECTIVES) == 0) + { + theToken = HandleDirective(); + } + else + { + if ((m_flags & TKF_NODIRECTIVES) != 0) + { + return HandleSymbol('#'); + } + SkipToLineEnd(); + } + break; + case '/': + theToken = HandleSlash(); + break; + case '"': + theToken = HandleString(); + break; + case '\'': + theToken = HandleQuote(); + break; + default: + if (((theByte >= 'a') && (theByte <= 'z')) + || ((theByte >= 'A') && (theByte <= 'Z')) + || ((theByte == '_') && ((m_flags & TKF_NOUNDERSCOREINIDENTIFIER) == 0))) + { + theToken = HandleIdentifier(theByte); + } + else if (((m_flags & TKF_NUMERICIDENTIFIERSTART) != 0) && (theByte >= '0') && (theByte <= '9')) + { + theToken = HandleIdentifier(theByte); + } + else if (((theByte >= '0') && (theByte <= '9')) || (theByte == '-')) + { + theToken = HandleNumeric(theByte); + } + else if (theByte == '.') + { + theToken = HandleDecimal(); + } + else if (theByte <= ' ') + { + break; + } + else + { + theToken = HandleSymbol(theByte); + } + } + if (theToken != NULL) + { + return theToken; + } + } +} + +bool CTokenizer::NextChar(byte& theByte) +{ + while (m_curParseStream != NULL) + { + if (m_curParseStream->NextChar(theByte)) + { + return true; + } + CParseStream* curParseStream = m_curParseStream; + m_curParseStream = curParseStream->GetNext(); + curParseStream->Delete(); + } + return false; +} + +bool CTokenizer::RequireToken(int tokenType) +{ + CToken* theToken = GetToken(); + bool retValue = theToken->GetType() == tokenType; + theToken->Delete(); + return retValue; +} + +void CTokenizer::ScanUntilToken(int tokenType) +{ + CToken* curToken; + int tokenValue = TK_UNDEFINED; + while (tokenValue != tokenType) + { + curToken = GetToken(); + tokenValue = curToken->GetType(); + if (tokenValue == TK_EOF) + { + PutBackToken(curToken); + break; + } + if (tokenValue == tokenType) + { + PutBackToken(curToken); + } + else + { + curToken->Delete(); + } + } +} + +void CTokenizer::Init(UINT dwFlags) +{ + m_symbolLookup = NULL; + m_nextToken = NULL; + m_curParseStream = NULL; + m_keywords = NULL; + m_symbols = NULL; + m_errors = NULL; + m_state = NULL; + m_flags = dwFlags; + m_errorProc = NULL; +} + +void CTokenizer::SetErrorProc(LPTokenizerErrorProc errorProc) +{ + m_errorProc = errorProc; +} + +void CTokenizer::SetAdditionalErrors(keywordArray_t* theErrors) +{ + m_errors = theErrors; +} + +keywordArray_t* CTokenizer::SetKeywords(keywordArray_t* theKeywords) +{ + keywordArray_t* retval = m_keywords; + m_keywords = theKeywords; + return retval; +} + +void CTokenizer::SetSymbols(keywordArray_t* theSymbols) +{ + m_symbols = theSymbols; + if (m_symbolLookup != NULL) + { + m_symbolLookup->Delete(); + m_symbolLookup = NULL; + } + int i = 0; + if (theSymbols == NULL) + { + return; + } + while(theSymbols[i].m_tokenvalue != TK_EOF) + { + InsertSymbol(theSymbols[i].m_keyword, theSymbols[i].m_tokenvalue); + i++; + } +} + +void CTokenizer::InsertSymbol(LPCTSTR theSymbol, int theValue) +{ + CSymbolLookup** curHead = &m_symbolLookup; + CSymbolLookup* curParent = NULL; + CSymbolLookup* curLookup = NULL; + + for (UINT i = 0; i < strlen(theSymbol); i++) + { + bool found = false; + curLookup = *curHead; + while (curLookup != NULL) + { + if (curLookup->GetByte() == theSymbol[i]) + { + found = true; + break; + } + curLookup = curLookup->GetNext(); + } + if (!found) + { + curLookup = CSymbolLookup::Create(theSymbol[i]); + curLookup->SetParent(curParent); + curLookup->SetNext(*curHead); + *curHead = curLookup; + } + curHead = curLookup->GetChildAddress(); + curParent = curLookup; + } + if (curLookup->GetValue() != -1) + { + Error(TKERR_DUPLICATESYMBOL); + } + curLookup->SetValue(theValue); +} + +CToken* CTokenizer::HandleString() +{ + char theString[MAX_STRING_LENGTH]; + for (int i = 0; i < MAX_STRING_LENGTH; i++) + { + if (!NextChar((byte&)theString[i])) + { + return NULL; + } + if (theString[i] == '"') + { + theString[i] = '\0'; + return CStringToken::Create(theString); + } + if (theString[i] == '\\') + { + theString[i] = Escapement(); + } + } + Error(TKERR_STRINGLENGTHEXCEEDED); + return NULL; +} + +void CTokenizer::GetCurFilename(char** filename) +{ + if (m_curParseStream == NULL) + { + *filename = (char*)malloc(1); + *filename[0] = '\0'; + return; + } + m_curParseStream->GetCurFilename(filename); +} + +int CTokenizer::GetCurLine() +{ + if (m_curParseStream == NULL) + { + return 0; + } + return m_curParseStream->GetCurLine(); +} + +void CTokenizer::PutBackChar(byte theByte, int curLine, LPCTSTR filename) +{ + CParseStream* newStream; + if (filename == NULL) + { + curLine = m_curParseStream->GetCurLine(); + char* theFile = NULL; + m_curParseStream->GetCurFilename(&theFile); + newStream = CParsePutBack::Create(theByte, curLine, theFile); + if (theFile != NULL) + { + free(theFile); + } + } + else + { + newStream = CParsePutBack::Create(theByte, curLine, filename); + } + newStream->SetNext(m_curParseStream); + m_curParseStream = newStream; +} + +byte CTokenizer::Escapement() +{ + byte theByte; + if (NextChar(theByte)) + { + switch(theByte) + { + case 'n': + return '\n'; + case '\\': + return '\\'; + case '\'': + return '\''; + case '?': + return '\?'; + case 'a': + return '\a'; + case 'b': + return '\b'; + case 'f': + return '\f'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'v': + return '\v'; + case '0': + return '\0'; + // support for octal or hex sequences?? \000 or \xhhh + default: + PutBackChar(theByte); + } + } + return '\\'; +} + +bool CTokenizer::AddDefineSymbol(CDirectiveSymbol* definesymbol) +{ + CParseStream* curStream = m_curParseStream; + while(curStream != NULL) + { + if (curStream->IsThisDefinition(definesymbol)) + { + return false; + } + curStream = curStream->GetNext(); + } + CParseStream* newStream = CParseDefine::Create(definesymbol); + newStream->SetNext(m_curParseStream); + m_curParseStream = newStream; + return true; +} + +CToken* CTokenizer::TokenFromName(LPCTSTR name) +{ + CDirectiveSymbol* defineSymbol = (CDirectiveSymbol*)m_defines.FindSymbol(name); + if (defineSymbol != NULL) + { + if (AddDefineSymbol(defineSymbol)) + { + return FetchToken(); + } + } + if ((m_keywords != NULL) && ((m_flags & TKF_IGNOREKEYWORDS) == 0)) + { + int i = 0; + if ((m_flags & TKF_NOCASEKEYWORDS) == 0) + { + while (m_keywords[i].m_tokenvalue != TK_EOF) + { + if (strcmp(m_keywords[i].m_keyword, name) == 0) + { + return CUserToken::Create(m_keywords[i].m_tokenvalue, name); + } + i++; + } + } + else + { + while (m_keywords[i].m_tokenvalue != TK_EOF) + { + if (stricmp(m_keywords[i].m_keyword, name) == 0) + { + return CUserToken::Create(m_keywords[i].m_tokenvalue, name); + } + i++; + } + } + } + return CIdentifierToken::Create(name); +} + +int CTokenizer::DirectiveFromName(LPCTSTR name) +{ + if (directiveKeywords != NULL) + { + int i = 0; + while (directiveKeywords[i].m_tokenvalue != TK_EOF) + { + if (strcmp(directiveKeywords[i].m_keyword, name) == 0) + { + return directiveKeywords[i].m_tokenvalue; + } + i++; + } + } + return -1; +} + +CToken* CTokenizer::HandleIdentifier(byte theByte) +{ + char theString[MAX_IDENTIFIER_LENGTH]; + theString[0] = theByte; + for (int i = 1; i < MAX_IDENTIFIER_LENGTH; i++) + { + if (NextChar((byte&)theString[i])) + { + if (((theString[i] != '_') || ((m_flags & TKF_NOUNDERSCOREINIDENTIFIER) == 0)) && + ((theString[i] != '-') || ((m_flags & TKF_NODASHINIDENTIFIER) == 0))) + { + if (((theString[i] >= 'A') && (theString[i] <= 'Z')) + || ((theString[i] >= 'a') && (theString[i] <= 'z')) + || ((theString[i] >= '0') && (theString[i] <= '9')) + || (theString[i] == '_') || (theString[i] == '-')) + { + continue; + } + } + PutBackChar(theString[i]); + } + theString[i] = '\0'; + return TokenFromName(theString); + } + Error(TKERR_IDENTIFIERLENGTHEXCEEDED); + return NULL; +} + +CToken* CTokenizer::HandleSlash() +{ + byte theByte; + if (!NextChar(theByte)) + { + return NULL; + } + if (theByte == '/') + { + if (m_flags & TKF_COMMENTTOKENS) + { + return GetToEndOfLine(TK_COMMENT); + } + SkipToLineEnd(); + return NULL; + } + if (theByte == '*') + { + if (m_flags & TKF_COMMENTTOKENS) + { + char theString[MAX_IDENTIFIER_LENGTH + 1]; + theString[0] = ' '; + while (theString[0] == ' ') + { + if (!NextChar((byte&)theString[0])) + { + return NULL; + } + } + for (int i = 1; i < MAX_IDENTIFIER_LENGTH; i++) + { + if (NextChar((byte&)theString[i])) + { + if (theString[i] != '*') + { + continue; + } + i++; + if (NextChar((byte&)theString[i])) + { + if (theString[i] == '/') + { + i--; + theString[i] = '\0'; + return CCommentToken::Create(theString); + } + } + } + } + Error(TKERR_IDENTIFIERLENGTHEXCEEDED); + return NULL; + } + while(NextChar(theByte)) + { + while(theByte == '*') + { + if (!NextChar(theByte)) + { + break; + } + if (theByte == '/') + { + return NULL; + } + } + } + return NULL; + } + PutBackChar(theByte); + return HandleSymbol('/'); +} + +CToken* CTokenizer::HandleNumeric(byte theByte) +{ + bool thesign = theByte == '-'; + if (thesign) + { + if (!NextChar(theByte)) + { + return HandleSymbol('-'); + } + if (theByte == '.') + { + return HandleDecimal(thesign); + } + if ((theByte < '0') || (theByte > '9')) + { + PutBackChar(theByte); + return HandleSymbol('-'); + } + } + if (theByte == '0') + { + return HandleOctal(thesign); + } + long value = 0; + bool digithit = false; + while((theByte >= '0') && (theByte <= '9')) + { + value = (value * 10) + (theByte - '0'); + if (!NextChar(theByte)) + { + if (thesign) + { + value = -value; + } + return CIntToken::Create(value); + } + digithit = true; + } + if (theByte == '.') + { + if (digithit) + { + return HandleFloat(thesign, value); + } + if (thesign) + { + PutBackChar(theByte); + theByte = '-'; + } + return HandleSymbol(theByte); + } + PutBackChar(theByte); + if (thesign) + { + value = -value; + } + return CIntToken::Create(value); +} + +CToken* CTokenizer::HandleOctal(bool thesign) +{ + byte theByte; + int value = 0; + + if (!NextChar(theByte)) + { + return CIntToken::Create(value); + } + if (theByte == '.') + { + return HandleDecimal(thesign); + } + if ((theByte == 'x') || (theByte == 'X')) + { + return HandleHex(thesign); + } + while(true) + { + if((theByte >= '0') && (theByte <='7')) + { + value = (value * 8) + (theByte - '0'); + } + else + { + PutBackChar(theByte); + if (thesign) + { + value = -value; + } + return CIntToken::Create(value); + } + if (!NextChar(theByte)) + { + if (thesign) + { + value = -value; + } + return CIntToken::Create(value); + } + } +} + +CToken* CTokenizer::HandleHex(bool thesign) +{ + int value = 0; + + while (true) + { + byte theByte; + if (!NextChar(theByte)) + { + if (thesign) + { + value = -value; + } + return CIntToken::Create(value); + } + switch (theByte) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + theByte = theByte - '0'; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + theByte = theByte - 'A' + 10; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + theByte = theByte - 'a' + 10; + break; + default: + PutBackChar(theByte); + if (thesign) + { + value = -value; + } + return CIntToken::Create(value); + } + value = (value * 16) + theByte; + } +} + +CToken* CTokenizer::HandleDecimal(bool thesign) +{ + byte theByte; + if (!NextChar(theByte)) + { + if (thesign) + { + PutBackChar('.'); + return HandleSymbol('-'); + } + HandleSymbol('.'); + } + PutBackChar(theByte); + if ((theByte <= '9') && (theByte >= '0')) + { + return HandleFloat(thesign); + } + if (thesign) + { + PutBackChar('.'); + theByte = '-'; + } + else + { + theByte = '.'; + } + return HandleSymbol(theByte); +} + +CToken* CTokenizer::HandleFloat(bool thesign, long value) +{ + float lower = 1.0; + float newValue = (float)value; + byte theByte; + while(NextChar(theByte)) + { + if ((theByte >= '0') && (theByte <= '9')) + { + lower = lower / 10; + newValue = newValue + ((theByte - '0') * lower); + continue; + } + PutBackChar(theByte); + break; + } + if (thesign) + { + newValue = -newValue; + } + return CFloatToken::Create(newValue); +} + +CToken* CTokenizer::HandleQuote() +{ + byte theByte; + if (!NextChar(theByte)) + { + Error(TKERR_EXPECTED_CHAR); + return NULL; + } + if (theByte == '\\') + { + theByte = Escapement(); + } + byte dummy; + if (!NextChar(dummy)) + { + Error(TKERR_EXPECTED_CHAR); + return NULL; + } + if (dummy != '\'') + { + PutBackChar(dummy); + PutBackChar(theByte); + Error(TKERR_EXPECTED_CHAR); + return NULL; + } + return CCharToken::Create(theByte); +} + +void CTokenizer::SetFlags(UINT flags) +{ + m_flags = flags; +} + +UINT CTokenizer::GetFlags() +{ + return m_flags; +} + +CToken* CTokenizer::HandleSymbol(byte theByte) +{ + char symbolString[128]; + int curStrLen = 0; + symbolString[0] = '\0'; + bool consumed = false; + + CSymbolLookup* curLookup; + if ((m_flags & TKF_RAWSYMBOLSONLY) == 0) + { + curLookup = m_symbolLookup; + } + else + { + curLookup = NULL; + } + CSymbolLookup* lastLookup = NULL; + while(curLookup != NULL) + { + if (curLookup->GetByte() == theByte) + { + symbolString[curStrLen++] = theByte; + symbolString[curStrLen] = '\0'; + lastLookup = curLookup; + consumed = true; + if (curLookup->GetChild() == NULL) + { + break; + } + if (!NextChar(theByte)) + { + PutBackToken(CToken::Create()); + break; + } + consumed = false; + curLookup = curLookup->GetChild(); + continue; + } + curLookup = curLookup->GetNext(); + } + if ((!consumed) && (lastLookup != NULL)) + { + PutBackChar(theByte); + } + while ((lastLookup != NULL) && (lastLookup->GetValue() == -1)) + { + curStrLen--; + symbolString[curStrLen] = '\0'; + // symbolString = symbolString.Left(symbolString.GetLength() - 1); + if (lastLookup->GetParent() == NULL) + { + if ((m_flags & TKF_WANTUNDEFINED) == 0) + { + Error(TKERR_UNRECOGNIZEDSYMBOL); + } + } + else + { + PutBackChar(lastLookup->GetByte()); + } + lastLookup = lastLookup->GetParent(); + } + if (lastLookup == NULL) + { + if ((m_flags & TKF_WANTUNDEFINED) == 0) + { + return NULL; + } + curStrLen = 0; + symbolString[curStrLen++] = char(theByte); + symbolString[curStrLen] = '\0'; + if ((m_flags & TKF_WIDEUNDEFINEDSYMBOLS) != 0) + { + while (true) + { + if (!NextChar(theByte)) + { + PutBackToken(CToken::Create()); + break; + } + if (theByte == ' ') + { + break; + } + if (((theByte >= 'a') && (theByte <= 'z')) || ((theByte >= 'A') && (theByte <= 'Z')) || + ((theByte >= '0') && (theByte <= '9'))) + { + PutBackChar(theByte); + break; + } + if (theByte < ' ') + { + if ((theByte == '\n') && ((TKF_USES_EOL & m_flags) != 0)) + { + PutBackToken(CUserToken::Create(TK_EOL, "-EOLN-")); + break; + } + continue; + } + symbolString[curStrLen++] = theByte; + symbolString[curStrLen] = '\0'; + } + } + return CUndefinedToken::Create(symbolString); + } + return CUserToken::Create(lastLookup->GetValue(), symbolString); +} + +CToken* CTokenizer::HandleDirective() +{ + int tokenValue = 0; + CToken* theToken = FetchToken(); + if (theToken->GetType() == TK_EOF) + { + return theToken; + } + if (theToken->GetType() != TK_IDENTIFIER) + { + Error(TKERR_INVALID_DIRECTIVE); + theToken->Delete(); + SkipToLineEnd(); + return NULL; + } + + CDirectiveSymbol* curSymbol; + CTokenizerState* state; + int theDirective = DirectiveFromName(theToken->GetStringValue()); + theToken->Delete(); + byte theByte; + switch(theDirective) + { + case DIR_INCLUDE: + if ((m_state != NULL) && (m_state->Skipping())) + { + break; + } + theToken = GetToken(); + if (theToken->GetType() != TK_STRING) + { + Error(TKERR_INCLUDE_FILE_NOTFOUND); + theToken->Delete(); + SkipToLineEnd(); + break; + } + AddParseFile(theToken->GetStringValue()); + theToken->Delete(); + break; + case DIR_IFDEF: + if ((m_state != NULL) && (m_state->Skipping())) + { + state = CTokenizerHolderState::Create(); + state->SetNext(m_state); + m_state = state; + break; + } + theToken = GetToken(); + if (theToken->GetType() != TK_IDENTIFIER) + { + Error(TKERR_EXPECTED_IDENTIFIER); + theToken->Delete(); + SkipToLineEnd(); + break; + } + state = CTokenizerState::Create(m_defines.FindSymbol(theToken->GetStringValue()) == NULL); + theToken->Delete(); + state->SetNext(m_state); + m_state = state; + break; + case DIR_IFNDEF: + if ((m_state != NULL) && (m_state->Skipping())) + { + state = CTokenizerHolderState::Create(); + state->SetNext(m_state); + m_state = state; + break; + } + theToken = GetToken(); + if (theToken->GetType() != TK_IDENTIFIER) + { + Error(TKERR_EXPECTED_IDENTIFIER); + theToken->Delete(); + SkipToLineEnd(); + break; + } + state = CTokenizerState::Create(m_defines.FindSymbol(theToken->GetStringValue()) != NULL); + theToken->Delete(); + state->SetNext(m_state); + m_state = state; + break; + case DIR_ENDIF: + if (m_state == NULL) + { + Error(TKERR_UNMATCHED_DIRECTIVE); + break; + } + state = m_state; + m_state = state->GetNext(); + state->Delete(); + break; + case DIR_ELSE: + if (m_state == NULL) + { + Error(TKERR_UNMATCHED_DIRECTIVE); + break; + } + if (!m_state->ProcessElse()) + { + Error(TKERR_UNMATCHED_DIRECTIVE); + break; + } + break; + case DIR_DEFINE: + if ((m_state != NULL) && (m_state->Skipping())) + { + break; + } + theToken = GetToken(); + if (theToken->GetType() != TK_IDENTIFIER) + { + Error(TKERR_EXPECTED_IDENTIFIER); + theToken->Delete(); + SkipToLineEnd(); + break; + } + // blind add - should check first for value changes + { + curSymbol = CDirectiveSymbol::Create(theToken->GetStringValue()); + char temp[128]; + int tempsize = 0; + while(NextChar(theByte)) + { + if (theByte == '\n') + { + break; + } + temp[tempsize++] = char(theByte); + } + temp[tempsize] = '\0'; + curSymbol->SetValue(temp); + if (!m_defines.AddSymbol(curSymbol)) + { + curSymbol->Delete(); + } + } + break; + case DIR_UNDEFINE: + if ((m_state != NULL) && (m_state->Skipping())) + { + break; + } + theToken = GetToken(); + if (theToken->GetType() != TK_IDENTIFIER) + { + Error(TKERR_EXPECTED_IDENTIFIER); + theToken->Delete(); + SkipToLineEnd(); + break; + } + m_defines.RemoveSymbol(theToken->GetStringValue()); + break; + default: + Error(TKERR_INVALID_DIRECTIVE); + SkipToLineEnd(); + break; + } + return NULL; +} + +COLORREF CTokenizer::ParseRGB() +{ + CToken* theToken = GetToken(); + if (theToken->GetType() != TK_INT) + { + Error(TKERR_EXPECTED_INTEGER); + theToken->Delete(); + return RGB(0, 0, 0); + } + int red = theToken->GetIntValue(); + theToken->Delete(); + theToken = GetToken(); + if (theToken->GetType() != TK_INT) + { + Error(TKERR_EXPECTED_INTEGER); + theToken->Delete(); + return RGB(0, 0, 0); + } + int green = theToken->GetIntValue(); + theToken->Delete(); + theToken = GetToken(); + if (theToken->GetType() != TK_INT) + { + Error(TKERR_EXPECTED_INTEGER); + theToken->Delete(); + return RGB(0, 0, 0); + } + int blue = theToken->GetIntValue(); + theToken->Delete(); + return RGB(red, green, blue); +} + +// +// CSymbolLookup +// + +CSymbolLookup::CSymbolLookup() +{ +} + +CSymbolLookup::~CSymbolLookup() +{ +} + +CSymbolLookup* CSymbolLookup::Create(byte theByte) +{ + CSymbolLookup* curLookup = new CSymbolLookup(); + curLookup->Init(theByte); + return curLookup; +} + +void CSymbolLookup::Delete() +{ + if (m_sibling != NULL) + { + m_sibling->Delete(); + m_sibling = NULL; + } + if (m_child != NULL) + { + m_child->Delete(); + m_child = NULL; + } + delete this; +} + +void CSymbolLookup::Init(byte theByte) +{ + m_parent = NULL; + m_child = NULL; + m_sibling = NULL; + m_value = -1; + m_byte = theByte; +} + +CSymbolLookup* CSymbolLookup::GetNext() +{ + return m_sibling; +} + +void CSymbolLookup::SetNext(CSymbolLookup* next) +{ + m_sibling = next; +} + +void CSymbolLookup::SetParent(CSymbolLookup* parent) +{ + m_parent = parent; +} + +void CSymbolLookup::SetValue(int value) +{ + m_value = value; +} + +int CSymbolLookup::GetValue() +{ + return m_value; +} + +byte CSymbolLookup::GetByte() +{ + return m_byte; +} + +CSymbolLookup** CSymbolLookup::GetChildAddress() +{ + return &m_child; +} + +CSymbolLookup* CSymbolLookup::GetChild() +{ + return m_child; +} + +CSymbolLookup* CSymbolLookup::GetParent() +{ + return m_parent; +} \ No newline at end of file diff --git a/codemp/icarus/blockstream.h b/codemp/icarus/blockstream.h new file mode 100644 index 0000000..b2ef76f --- /dev/null +++ b/codemp/icarus/blockstream.h @@ -0,0 +1,198 @@ +// BlockStream.h + +#ifndef __INTERPRETED_BLOCK_STREAM__ +#define __INTERPRETED_BLOCK_STREAM__ + +#pragma warning(disable : 4786) //identifier was truncated +#pragma warning(disable : 4514) //unreffed inline func removed + +#include "../qcommon/qcommon.h" +#include + +#pragma warning (push, 3) //go back down to 3 for the stl include +#include +#include +#pragma warning (pop) +using namespace std; + +#define IBI_EXT ".IBI" //(I)nterpreted (B)lock (I)nstructions +#define IBI_HEADER_ID "IBI" + +const float IBI_VERSION = 1.57f; +const int MAX_FILENAME_LENGTH = 1024; + +typedef float vector_t[3]; + +enum +{ + POP_FRONT, + POP_BACK, + PUSH_FRONT, + PUSH_BACK +}; + +// Templates + +// CBlockMember + +class CBlockMember +{ +public: + + CBlockMember(); + ~CBlockMember(); + + void Free( void ); + + int WriteMember ( FILE * ); //Writes the member's data, in block format, to FILE * + int ReadMember( char **, long * ); //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 * ); + void SetData( vector_t ); + void SetData( void *data, int size ); + + 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 + + inline void *operator new( size_t size ) + { // Allocate the memory. + return Z_Malloc( size, TAG_ICARUS4, qtrue ); + } + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Free the Memory. + Z_Free( pRawData ); + } + + CBlockMember *Duplicate( void ); + + template void WriteData(T &data) + { + if ( m_data ) + { + ICARUS_Free( m_data ); + } + + m_data = ICARUS_Malloc( sizeof(T) ); + *((T *) m_data) = data; + m_size = sizeof(T); + } + + template void WriteDataPointer(const T *data, int num) + { + if ( m_data ) + { + ICARUS_Free( m_data ); + } + + m_data = ICARUS_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(); + ~CBlock(); + + int Init( void ); + + int Create( int ); + int Free(); + + //Write Overloads + + int Write( int, vector_t ); + int Write( int, float ); + int Write( int, const char * ); + int Write( int, int ); + int Write( CBlockMember * ); + + //Member push / pop functions + + int AddMember( CBlockMember * ); + CBlockMember *GetMember( int memberNum ); + + void *GetMemberData( int memberNum ); + + CBlock *Duplicate( void ); + + 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; } + +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(); + ~CBlockStream(); + + int Init( void ); + + int Create( char * ); + int Free( void ); + + // Stream I/O functions + + int BlockAvailable( void ); + + int WriteBlock( CBlock * ); //Write the block out + int ReadBlock( CBlock * ); //Read the block in + + int Open( char *, long ); //Open a stream for reading / writing + +protected: + + unsigned GetUnsignedInteger( void ); + int GetInteger( void ); + + char GetChar( void ); + long GetLong( void ); + float GetFloat( void ); + + void StripExtension( const char *, char * ); //Utility function to strip away file extensions + + long m_fileSize; //Size of the file + FILE *m_fileHandle; //Global file handle of current I/O source + char m_fileName[MAX_FILENAME_LENGTH]; //Name of the current file + + char *m_stream; //Stream of data to be parsed + long m_streamPos; +}; + +#endif //__INTERPRETED_BLOCK_STREAM__ \ No newline at end of file diff --git a/codemp/icarus/icarus.h b/codemp/icarus/icarus.h new file mode 100644 index 0000000..fdb93b0 --- /dev/null +++ b/codemp/icarus/icarus.h @@ -0,0 +1,32 @@ +// ICARUS Public Header File + +#pragma warning ( disable : 4786 ) //NOTENOTE: STL Debug name length warning + +#ifndef __ICARUS__ +#define __ICARUS__ + +#include "../game/g_public.h" + +#pragma warning( disable : 4786 ) // identifier was truncated +#pragma warning( disable : 4514 ) // unreferenced inline was removed +#pragma warning( disable : 4710 ) // not inlined + +#pragma warning( push, 3 ) //save current state and change to 3 + +#define STL_ITERATE( a, b ) for ( a = b.begin(); a != b.end(); a++ ) +#define STL_INSERT( a, b ) a.insert( a.end(), b ); + +#include "tokenizer.h" +#include "blockstream.h" +#include "interpreter.h" +#include "sequencer.h" +#include "taskmanager.h" +#include "instance.h" + +#pragma warning( pop ) //restore + + +extern void *ICARUS_Malloc(int iSize); +extern void ICARUS_Free(void *pMem); + +#endif //__ICARUS__ diff --git a/codemp/icarus/instance.h b/codemp/icarus/instance.h new file mode 100644 index 0000000..0ca33f2 --- /dev/null +++ b/codemp/icarus/instance.h @@ -0,0 +1,81 @@ +// ICARUS Intance header + +#ifndef __INSTANCE__ +#define __INSTANCE__ + +#include "blockstream.h" +#include "interface.h" +#include "taskmanager.h" +#include "sequence.h" +#include "sequencer.h" + +class ICARUS_Instance +{ +public: + + typedef list< CSequence * > sequence_l; + typedef list< CSequencer * > sequencer_l; + typedef map < string, unsigned char > signal_m; + + ICARUS_Instance( void ); + ~ICARUS_Instance( void ); + + static ICARUS_Instance *Create( interface_export_t * ); + int Delete( void ); + + CSequencer *GetSequencer( int ); + void DeleteSequencer( CSequencer * ); + + CSequence *GetSequence( void ); + CSequence *GetSequence( int id ); + void DeleteSequence( CSequence * ); + + interface_export_t *GetInterface( void ) const { return m_interface; } + + //These are overriddable for "worst-case" save / loads + virtual int Save( void /*FIXME*/ ); + virtual int Load( void /*FIXME*/ ); + + void Signal( const char *identifier ); + bool CheckSignal( const char *identifier ); + void ClearSignal( const char *identifier ); + +protected: + + virtual int SaveSignals( void ); + virtual int SaveSequences( void ); + virtual int SaveSequenceIDTable( void ); + virtual int SaveSequencers( void ); + + int AllocateSequences( int numSequences, int *idTable ); + + virtual int LoadSignals( void ); + virtual int LoadSequencers( void ); + virtual int LoadSequences( void ); + virtual int LoadSequence( void ); + + int Free( void ); + + interface_export_t *m_interface; + int m_GUID; + + sequence_l m_sequences; + sequencer_l m_sequencers; + + signal_m m_signals; + +#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 + +}; + +#endif //__INSTANCE__ diff --git a/codemp/icarus/interface.h b/codemp/icarus/interface.h new file mode 100644 index 0000000..645d935 --- /dev/null +++ b/codemp/icarus/interface.h @@ -0,0 +1,72 @@ +// ICARUS Interface header file + +#ifndef __INTERFACE__ +#define __INTERFACE__ + +//#include "../server/server.h" +//#include "../game/g_public.h" + +typedef unsigned long DWORD; + +typedef float vec_t; +typedef vec_t vec3_t[3]; + +class CSequencer; +class CTaskManager; + +typedef struct interface_export_s +{ + //General + int (*I_LoadFile)( const char *name, void **buf ); + void (*I_CenterPrint)( const char *format, ... ); + void (*I_DPrintf)( int, const char *, ... ); + sharedEntity_t *(*I_GetEntityByName)( const char *name ); //Polls the engine for the sequencer of the entity matching the name passed + DWORD (*I_GetTime)( void ); //Gets the current time + DWORD (*I_GetTimeScale)(void ); + int (*I_PlaySound)( int taskID, int entID, const char *name, const char *channel ); + void (*I_Lerp2Pos)( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ); + void (*I_Lerp2Origin)( int taskID, int entID, vec3_t origin, float duration ); + void (*I_Lerp2Angles)( int taskID, int entID, vec3_t angles, float duration ); + int (*I_GetTag)( int entID, const char *name, int lookup, vec3_t info ); + void (*I_Lerp2Start)( int taskID, int entID, float duration ); + void (*I_Lerp2End)( int taskID, int entID, float duration ); + void (*I_Set)( int taskID, int entID, const char *type_name, const char *data ); + void (*I_Use)( int entID, const char *name ); + void (*I_Kill)( int entID, const char *name ); + void (*I_Remove)( int entID, const char *name ); + float (*I_Random)( float min, float max ); + void (*I_Play)( int taskID, int entID, const char *type, const char *name ); + + //Camera functions + void (*I_CameraPan)( vec3_t angles, vec3_t dir, float duration ); + void (*I_CameraMove)( vec3_t origin, float duration ); + void (*I_CameraZoom)( float fov, float duration ); + void (*I_CameraRoll)( float angle, float duration ); + void (*I_CameraFollow)( const char *name, float speed, float initLerp ); + void (*I_CameraTrack)( const char *name, float speed, float initLerp ); + void (*I_CameraDistance)( float dist, float initLerp ); + void (*I_CameraFade)( float sr, float sg, float sb, float sa, float dr, float dg, float db, float da, float duration ); + void (*I_CameraPath)( const char *name ); + void (*I_CameraEnable)( void ); + void (*I_CameraDisable)( void ); + void (*I_CameraShake)( float intensity, int duration ); + + int (*I_GetFloat)( int entID, int type, const char *name, float *value ); + int (*I_GetVector)( int entID, int type, const char *name, vec3_t value ); + int (*I_GetString)( int entID, int type, const char *name, char **value ); + + int (*I_Evaluate)( int p1Type, const char *p1, int p2Type, const char *p2, int operatorType ); + + void (*I_DeclareVariable)( int type, const char *name ); + void (*I_FreeVariable)( const char *name ); + + //Save / Load functions + + int (*I_WriteSaveData)( unsigned long chid, void *data, int length ); + // Below changed by BTO (VV). Visual C++ 7.1 compiler no longer allows default args on function pointers. Ack. + int (*I_ReadSaveData)( unsigned long chid, void *address, int length /* , void **addressptr = NULL */ ); + int (*I_LinkEntity)( int entID, CSequencer *sequencer, CTaskManager *taskManager ); + +} interface_export_t; + +#endif //__INTERFACE__ \ No newline at end of file diff --git a/codemp/icarus/interpreter.h b/codemp/icarus/interpreter.h new file mode 100644 index 0000000..913dbca --- /dev/null +++ b/codemp/icarus/interpreter.h @@ -0,0 +1,224 @@ +// Interpreter.h + +#ifndef __INTERPRETER__ +#define __INTERPRETER__ + +#define ICARUS_VERSION 1.33 + +#define MAX_STRING_SIZE 256 +#define MAX_VAR_NAME 64 + +typedef float vector_t[3]; + +//If you modify this, you MUST modify in g_ICARUScb.c as well. +//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, + NUM_TYPES +}; + +enum +{ + MSG_COMPLETED, + MSG_EOF, + NUM_MESSAGES, +}; + +#ifdef __cplusplus +typedef struct variable_s +{ + char name[MAX_VAR_NAME]; + int type; + void *data; +} variable_t; + +typedef map< string, variable_t * > variable_m; +typedef vector < variable_t * > variable_v; + +//CInterpreter + +class CInterpreter +{ +public: + + CInterpreter(); + ~CInterpreter(); + + int Interpret( CTokenizer *, CBlockStream *, char *filename=NULL ); //Main interpretation function + + int Match( int ); //Looks ahead to the next token to try and match it to the passed token, consumes token on success + int LookAhead( int ); //Looks ahead without consuming on success + + int FindSymbol( const char *, keywordArray_t * ); //Searches the symbol table for the given name. Returns the ID if found + + int GetAffect( void ); //Handles the affect() function + int GetWait( void ); //Handles the wait() function + int GetSet( void ); //Handles the set() function + int GetBroadcast( void ); //Handles the broadcast() function + int GetLoop( void ); //Handles the loop() function + int GetPrint( void ); //Handles the print() function + int GetUse( void ); //Handles the use() function + int GetFlush( void ); //Handles the flush() function + int GetRun( void ); //Handles the run() function + int GetKill( void ); //Handles the kill() function + int GetRemove( void ); //Handles the remove() function + int GetCamera( void ); //Handles the camera() function + int GetIf( void ); //Handles the if() conditional statement + int GetSound( void ); //Handles the sound() function + int GetMove( void ); //Handles the move() function + int GetRotate( void ); //Handles the rotate() function + int GetRem( void ); //Handles the rem() function + int GetTask( void ); + int GetDo( void ); + int GetElse( void ); + int GetDeclare( void ); + int GetFree( void ); + int GetDoWait( void ); + int GetSignal( void ); + int GetWaitSignal( void ); + int GetPlay( void ); + + int GetRandom( CBlock *block ); + int GetGet( CBlock *block ); //Heh + int GetTag( CBlock *block ); //Handles the tag() identifier + int GetVector( CBlock *block ); + + int GetNextType( void ); + + int GetType( char *get ); + + int GetAny( CBlock *block ); + int GetEvaluator( CBlock *block ); + int GetString( CBlock *); //Attempts to match and retrieve the value of a string token + int GetIdentifier( CBlock *get ); //Attempts to match and retrieve the value of an identifier token + int GetInteger( CBlock * ); //Attempts to match and retrieve the value of a int token + int GetFloat( CBlock * ); //Attempts to match and retrieve the value of a float token + int GetVariable( int type ); + + int GetID ( char * ); //Attempts to match and interpret an identifier + + keywordArray_t *GetSymbols( void ) { return (keywordArray_t *) &m_symbolKeywords; } //Returns the interpreter's symbol table + keywordArray_t *GetIDs( void ) { return (keywordArray_t *) &m_IDKeywords; } //Returns the interpreter's ID table + keywordArray_t *GetTypes( void ) { return (keywordArray_t *) &m_typeKeywords; } //Returns the interpreter's type table + +protected: + + void InitVars( void ); + void FreeVars( void ); + + variable_t *AddVar( const char *name, int type ); + variable_t *FindVar( const char *name ); + + const char *GetTokenName( int ); //Returns the name of a token + int Error( char *, ... ); //Prints an error message + + int MatchTag( void ); //Attempts to match to a tag identifier + int MatchGet( void ); //Attempts to match to a get identifier + int MatchRandom( void ); //Attempts to match to a random identifier + + CTokenizer *m_tokenizer; //Pointer to the tokenizer + CBlockStream *m_blockStream; //Pointer to the block stream + + variable_v m_vars; + variable_m m_varMap; + + string m_sCurrentLine; // used in IBIze error reporting for more clarity + string m_sCurrentFile; // full-pathed name of .TXT file (needed because of above, which affects parsestreams) + int m_iCurrentLine; // also needed now because of 'm_sCurrentLine' + int m_iBadCBlockNumber; // used for final app return code (NZ = err) + + static keywordArray_t m_symbolKeywords[]; //Symbols + static keywordArray_t m_IDKeywords[]; //Identifiers + static keywordArray_t m_typeKeywords[]; //Types + static keywordArray_t m_conditionalKeywords[]; //Conditional +}; + +#endif __cplusplus +#endif //__INTERPRETER__ diff --git a/codemp/icarus/module.h b/codemp/icarus/module.h new file mode 100644 index 0000000..6ae5319 --- /dev/null +++ b/codemp/icarus/module.h @@ -0,0 +1 @@ +//This needs to be present to make the tokenizer happy... \ No newline at end of file diff --git a/codemp/icarus/sequence.h b/codemp/icarus/sequence.h new file mode 100644 index 0000000..0c137fa --- /dev/null +++ b/codemp/icarus/sequence.h @@ -0,0 +1,98 @@ +// Sequence Header File + +#ifndef __SEQUENCE__ +#define __SEQUENCE__ + +#include "blockstream.h" +#include "interface.h" +#include "taskmanager.h" + +class ICARUS_Instance; + +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( void ); + + //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 *GetChildByIndex( int id ); + bool HasChild( CSequence *sequence ); + + void SetOwner( ICARUS_Instance *owner ) { m_owner = owner; } + + int Save( void ); + int Load( void ); + + inline void *operator new( size_t size ) + { // Allocate the memory. + return Z_Malloc( size, TAG_ICARUS3, qtrue ); + } + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Free the Memory. + Z_Free( pRawData ); + } + +protected: + + int SaveCommand( CBlock *block ); + + ICARUS_Instance *m_owner; + + //Organization information + sequence_l m_children; + 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/codemp/icarus/sequencer.h b/codemp/icarus/sequencer.h new file mode 100644 index 0000000..0730a9b --- /dev/null +++ b/codemp/icarus/sequencer.h @@ -0,0 +1,189 @@ +// Sequencer Header File + +#ifndef __SEQUENCER__ +#define __SEQUENCER__ + +#include "blockstream.h" +#include "interface.h" +#include "taskmanager.h" +#include "sequence.h" + +#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 +#pragma warning (pop) +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated +using namespace std; + +//Defines + +#define SQ_COMMON 0x00000000 //Common one-pass sequence +#define SQ_LOOP 0x00000001 //Looping sequence +#define SQ_RETAIN 0x00000002 //Inside a looping sequence list, retain the information +#define SQ_AFFECT 0x00000004 //Affect sequence +#define SQ_RUN 0x00000008 //A run block +#define SQ_PENDING 0x00000010 //Pending use, don't free when flushing the sequences +#define SQ_CONDITIONAL 0x00000020 //Conditional statement +#define SQ_TASK 0x00000040 //Task block + +#define BF_ELSE 0x00000001 //Block has an else id //FIXME: This was a sloppy fix for a problem that arose from conditionals + +#define S_FAILED(a) (a!=SEQ_OK) + +//const int MAX_ERROR_LENGTH = 256; + +//Typedefs + +typedef struct bstream_s +{ + CBlockStream *stream; + bstream_s *last; +} bstream_t; + +//Enumerations + +enum +{ + SEQ_OK, //Command was successfully added + SEQ_FAILED, //An error occured while trying to insert the command +}; + +// Sequencer + +class ICARUS_Instance; + +/* +================================================================================================== + + CSequencer + +================================================================================================== +*/ + +class CSequencer +{ +// typedef map < int, CSequence * > sequenceID_m; + typedef list < CSequence * > sequence_l; + typedef map < CTaskGroup *, CSequence * > taskSequence_m; + +public: + + CSequencer(); + ~CSequencer(); + + int Init( int ownerID, interface_export_t *ie, CTaskManager *taskManager, ICARUS_Instance *iCARUS ); + static CSequencer *Create ( void ); + int Free( void ); + + int Run( char *buffer, long size ); + int Callback( CTaskManager *taskManager, CBlock *block, int returnCode ); + + ICARUS_Instance *GetOwner( void ) { return m_owner; } + + void SetOwnerID( int owner ) { m_ownerID = owner;} + + int GetOwnerID( void ) const { return m_ownerID; } + interface_export_t *GetInterface( void ) const { return m_ie; } + CTaskManager *GetTaskManager( void ) const { return m_taskManager; } + + void SetTaskManager( CTaskManager *tm) { if ( tm ) m_taskManager = tm; } + + int Save( void ); + int Load( void ); + + // Overloaded new operator. + inline void *operator new( size_t size ) + { // Allocate the memory. + return Z_Malloc( size, TAG_ICARUS2, qtrue ); + } + // Overloaded delete operator. + inline void operator delete( void *pRawData ) + { // Free the Memory. + Z_Free( pRawData ); + } + +// moved to public on 2/12/2 to allow calling during shutdown + int Recall( void ); +protected: + + int EvaluateConditional( CBlock *block ); + + int Route( CSequence *sequence, bstream_t *bstream ); + int Flush( CSequence *owner ); + void Interrupt( void ); + + bstream_t *AddStream( void ); + void DeleteStream( bstream_t *bstream ); + + int AddAffect( bstream_t *bstream, int retain, int *id ); + + CSequence *AddSequence( void ); + CSequence *AddSequence( CSequence *parent, CSequence *returnSeq, int flags ); + + CSequence *GetSequence( int id ); + + //NOTENOTE: This only removes references to the sequence, IT DOES NOT FREE THE ALLOCATED MEMORY! + int RemoveSequence( CSequence *sequence); + int DestroySequence( CSequence *sequence); + + int PushCommand( CBlock *command, int flag ); + CBlock *PopCommand( int flag ); + + inline CSequence *ReturnSequence( CSequence *sequence ); + + void CheckRun( CBlock ** ); + void CheckLoop( CBlock ** ); + void CheckAffect( CBlock ** ); + void CheckIf( CBlock ** ); + void CheckDo( CBlock ** ); + void CheckFlush( CBlock ** ); + + void Prep( CBlock ** ); + + int Prime( CTaskManager *taskManager, CBlock *command ); + + void StripExtension( const char *in, char *out ); + + int ParseRun( CBlock *block ); + int ParseLoop( CBlock *block, bstream_t *bstream ); + int ParseAffect( CBlock *block, bstream_t *bstream ); + int ParseIf( CBlock *block, bstream_t *bstream ); + int ParseElse( CBlock *block, bstream_t *bstream ); + int ParseTask( CBlock *block, bstream_t *bstream ); + + int Affect( int id, int type ); + + void AddTaskSequence( CSequence *sequence, CTaskGroup *group ); + CSequence *GetTaskSequence( CTaskGroup *group ); + + //Member variables + + ICARUS_Instance *m_owner; + int m_ownerID; + + CTaskManager *m_taskManager; + interface_export_t *m_ie; //This is unique to the sequencer so that client side and server side sequencers could both + //operate under different interfaces (for client side scripting) + + 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; +}; + +#endif //__SEQUENCER__ \ No newline at end of file diff --git a/codemp/icarus/taskmanager.h b/codemp/icarus/taskmanager.h new file mode 100644 index 0000000..3dbe6ff --- /dev/null +++ b/codemp/icarus/taskmanager.h @@ -0,0 +1,191 @@ +// Task Manager header file + +#ifndef __TASK_MANAGER__ +#define __TASK_MANAGER__ + +#include + +#include "sequencer.h" +class CSequencer; + +#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; } + +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; } + +//protected: + + taskCallback_m m_completedTasks; + + CTaskGroup *m_parent; + + int m_numCompleted; + int m_GUID; +}; + +// CTaskManager + +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(); + + static CTaskManager *Create( void ); + + CBlock *GetCurrentTask( void ); + + int Init( CSequencer *owner ); + int Free( void ); + + int Flush( void ); + + int SetCommand( CBlock *block, int type ); + int Completed( int id ); + + int Update( void ); + qboolean IsRunning( void ); + + CTaskGroup *AddTaskGroup( const char *name ); + CTaskGroup *GetTaskGroup( const char *name ); + CTaskGroup *GetTaskGroup( int id ); + + int MarkTask( int id, int operation ); + CBlock *RecallTask( void ); + + void Save( void ); + void Load( void ); + +protected: + + int Go( void ); //Heartbeat function called once per game frame + int CallbackCommand( CTask *task, int returnCode ); + + inline bool Check( int targetID, CBlock *block, int memberNum ); + + int GetVector( int entID, CBlock *block, int &memberNum, vector_t &value ); + int GetFloat( int entID, CBlock *block, int &memberNum, float &value ); + int Get( int entID, CBlock *block, int &memberNum, char **value ); + + int PushTask( CTask *task, int flag ); + CTask *PopTask( int flag ); + + // Task functions + int Rotate( CTask *task ); + int Remove( CTask *task ); + int Camera( CTask *task ); + int Print( CTask *task ); + int Sound( CTask *task ); + int Move( CTask *task ); + int Kill( CTask *task ); + int Set( CTask *task ); + int Use( CTask *task ); + int DeclareVariable( CTask *task ); + int FreeVariable( CTask *task ); + int Signal( CTask *task ); + int Play( CTask *task ); + + int Wait( CTask *task, bool &completed ); + int WaitSignal( CTask *task, bool &completed ); + + 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; + + //CTask *m_waitTask; //Global pointer to the current task that is waiting for callback completion +}; + +#endif //__TASK_MANAGER__ diff --git a/codemp/icarus/tokenizer.h b/codemp/icarus/tokenizer.h new file mode 100644 index 0000000..8a7f68b --- /dev/null +++ b/codemp/icarus/tokenizer.h @@ -0,0 +1,601 @@ +// Tokenizer.h +// + +#ifndef __TOKENIZER_H +#define __TOKENIZER_H + +#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 +#pragma warning (pop) +#pragma warning (disable:4503) // decorated name length xceeded, name was truncated + +using namespace std; + +//#include +#include "../qcommon/platform.h" + +typedef unsigned char byte; +typedef unsigned short word; + +#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, +}; + +typedef struct +{ + char* m_keyword; + int m_tokenvalue; +} keywordArray_t; + +class lessstr +{ +public: + bool operator()(LPCTSTR str1, LPCTSTR str2) const {return (strcmp(str1, str2) < 0);}; +}; + +class CParseStream +{ +public: + CParseStream(); + ~CParseStream(); + static CParseStream* Create(); + virtual void Delete(); + virtual bool NextChar(byte& theByte); + virtual int GetCurLine(); + virtual void GetCurFilename(char** theBuff); + virtual long GetRemainingSize(); + + CParseStream* GetNext(); + void SetNext(CParseStream* next); + + virtual bool IsThisDefinition(void* theDefinition); + +protected: + virtual bool Init(); + + CParseStream* m_next; +}; + +class CToken +{ +public: + CToken(); + ~CToken(); + static CToken* Create(); + virtual void Delete(); + + virtual int GetType(); + CToken* GetNext(); + void SetNext(CToken* theToken); + virtual int GetIntValue(); + virtual LPCTSTR GetStringValue(); + virtual float GetFloatValue(); + +protected: + virtual void Init(); + + char* m_string; + CToken* m_next; +}; + +class CCharToken : public CToken +{ +public: + CCharToken(); + ~CCharToken(); + static CCharToken* Create(byte theByte); + virtual void Delete(); + + virtual int GetType(); + +protected: + virtual void Init(byte theByte); +}; + +class CStringToken : public CToken +{ +public: + CStringToken(); + ~CStringToken(); + static CStringToken* Create(LPCTSTR theString); + virtual void Delete(); + + virtual int GetType(); + +protected: + virtual void Init(LPCTSTR theString); +}; + +class CIntToken : public CToken +{ +public: + CIntToken(); + ~CIntToken(); + static CIntToken* Create(long value); + virtual void Delete(); + + virtual int GetType(); + virtual float GetFloatValue(); + virtual int GetIntValue(); + virtual LPCTSTR GetStringValue(); + +protected: + virtual void Init(long value); + + long m_value; +}; + +class CFloatToken : public CToken +{ +public: + CFloatToken(); + ~CFloatToken(); + static CFloatToken* Create(float value); + virtual void Delete(); + + virtual int GetType(); + virtual float GetFloatValue(); + virtual LPCTSTR GetStringValue(); + +protected: + virtual void Init(float value); + + float m_value; +}; + +class CIdentifierToken : public CToken +{ +public: + CIdentifierToken(); + ~CIdentifierToken(); + static CIdentifierToken* Create(LPCTSTR name); + virtual void Delete(); + + virtual int GetType(); + +protected: + virtual void Init(LPCTSTR name); +}; + +class CCommentToken : public CToken +{ +public: + CCommentToken(); + ~CCommentToken(); + static CCommentToken* Create(LPCTSTR name); + virtual void Delete(); + + virtual int GetType(); + +protected: + virtual void Init(LPCTSTR name); +}; + +class CUserToken : public CToken +{ +public: + CUserToken(); + ~CUserToken(); + static CUserToken* Create(int value, LPCTSTR string); + virtual void Delete(); + + virtual int GetType(); + +protected: + virtual void Init(int value, LPCTSTR string); + + int m_value; +}; + +class CUndefinedToken : public CToken +{ +public: + CUndefinedToken(); + ~CUndefinedToken(); + static CUndefinedToken* Create(LPCTSTR string); + virtual void Delete(); + + virtual int GetType(); + +protected: + virtual void Init(LPCTSTR string); +}; + +class CSymbol +{ +public: + CSymbol(); + virtual ~CSymbol(); + static CSymbol* Create(LPCTSTR symbolName); + virtual void Delete(); + + LPCTSTR GetName(); + +protected: + virtual void Init(LPCTSTR symbolName); + + char* m_symbolName; +}; + +typedef map symbolmap_t; + +class CDirectiveSymbol : public CSymbol +{ +public: + CDirectiveSymbol(); + ~CDirectiveSymbol(); + static CDirectiveSymbol* Create(LPCTSTR symbolName); + virtual void Delete(); + + void SetValue(LPCTSTR value); + LPCTSTR GetValue(); + +protected: + virtual void Init(LPCTSTR symbolName); + + char* m_value; +}; + +class CIntSymbol : public CSymbol +{ +public: + CIntSymbol(); + static CIntSymbol* Create(LPCTSTR symbolName, int value); + virtual void Delete(); + + int GetValue(); + +protected: + virtual void Init(LPCTSTR symbolName, int value); + + int m_value; +}; + +class CSymbolTable +{ +public: + CSymbolTable(); + ~CSymbolTable(); + static CSymbolTable* Create(); + void Delete(); + + bool AddSymbol(CSymbol* theSymbol); + CSymbol* FindSymbol(LPCTSTR symbolName); + CSymbol* ExtractSymbol(LPCTSTR symbolName); + void RemoveSymbol(LPCTSTR symbolName); + void DiscardSymbols(); + +protected: + void Init(); + symbolmap_t m_symbols; +}; + +class CSymbolLookup +{ +public: + CSymbolLookup(); + ~CSymbolLookup(); + static CSymbolLookup* Create(byte theByte); + virtual void Delete(); + CSymbolLookup* GetNext(); + void SetNext(CSymbolLookup* next); + void SetParent(CSymbolLookup* parent); + CSymbolLookup* GetParent(); + void SetValue(int value); + int GetValue(); + byte GetByte(); + +protected: + void Init(byte theByte); + + CSymbolLookup* m_child; + CSymbolLookup* m_sibling; + CSymbolLookup* m_parent; + int m_value; + byte m_byte; +}; + +class CTokenizerState +{ +public: + CTokenizerState(); + ~CTokenizerState(); + static CTokenizerState* Create(bool skip); + virtual void Delete(); + CTokenizerState* GetNext(); + void SetNext(CTokenizerState* next); + virtual bool ProcessElse(); + bool Skipping(); + +protected: + void Init(bool skip); + + bool m_skip; + bool m_elseHit; + CTokenizerState* m_next; +}; + +class CTokenizerHolderState : public CTokenizerState +{ +public: + CTokenizerHolderState(); + ~CTokenizerHolderState(); + static CTokenizerHolderState* Create(); + virtual void Delete(); + virtual bool ProcessElse(); + +protected: + void Init(); +}; + +typedef void (*LPTokenizerErrorProc)(LPCTSTR errString); + +#ifdef USES_MODULES +class CTokenizer : public CModule +#else +class CTokenizer +#endif +{ +public: + CTokenizer(); + ~CTokenizer(); + static CTokenizer* Create(UINT dwFlags = 0); + virtual void Delete(); + virtual void Error(int theError); + virtual void Error(int theError, LPCTSTR errString); + virtual void Error(LPCTSTR errString, int theError = TKERR_UNKNOWN); + + CToken* GetToken(UINT onFlags = 0, UINT offFlags = 0); + CToken* GetToken(keywordArray_t* keywords, UINT onFlags, UINT offFlags); + void PutBackToken(CToken* theToken, bool commented = false, LPCTSTR addedChars = NULL, bool bIgnoreThisTokenType = false); + bool RequireToken(int tokenType); + void ScanUntilToken(int tokenType); + void SkipToLineEnd(); + CToken* GetToEndOfLine(int tokenType = TK_IDENTIFIER); + + keywordArray_t* SetKeywords(keywordArray_t* theKeywords); + void SetSymbols(keywordArray_t* theSymbols); + void SetAdditionalErrors(keywordArray_t* theErrors); + void SetErrorProc(LPTokenizerErrorProc errorProc); + void AddParseStream(byte* data, long datasize); + bool AddParseFile(LPCTSTR filename); + COLORREF ParseRGB(); + long GetRemainingSize(); + + UINT GetFlags(); + void SetFlags(UINT flags); + + void GetCurFilename(char** filename); + int GetCurLine(); + + LPCTSTR LookupToken(int tokenID, keywordArray_t* theTable = NULL); + +protected: + void SetError(int theError, LPCTSTR errString); + virtual void Init(UINT dwFlags = 0); + CToken* FetchToken(); + bool AddDefineSymbol(CDirectiveSymbol* definesymbol); + bool NextChar(byte& theByte); + byte Escapement(); + void InsertSymbol(LPCTSTR theSymbol, int theValue); + void PutBackChar(byte theByte, int curLine = 0, LPCTSTR filename = NULL); + CToken* TokenFromName(LPCTSTR name); + CToken* HandleDirective(); + CToken* HandleSlash(); + CToken* HandleString(); + CToken* HandleQuote(); + CToken* HandleIdentifier(byte theByte); + CToken* HandleNumeric(byte theByte); + CToken* HandleFloat(bool thesign = false, long value = 0); + CToken* HandleDecimal(bool thesign = false); + CToken* HandleSymbol(byte theByte); + CToken* HandleHex(bool thesize); + CToken* HandleOctal(bool thesize); + int DirectiveFromName(LPCTSTR name); + + CParseStream* m_curParseStream; + keywordArray_t* m_keywords; + keywordArray_t* m_symbols; + keywordArray_t* m_errors; + CSymbolLookup* m_symbolLookup; + CToken* m_nextToken; + CSymbolTable m_defines; + CTokenizerState* m_state; + UINT m_flags; + LPTokenizerErrorProc m_errorProc; + + static keywordArray_t errorMessages[]; + static keywordArray_t directiveKeywords[]; +}; + +class CKeywordTable +{ +public: + CKeywordTable(CTokenizer* tokenizer, keywordArray_t* keywords); + ~CKeywordTable(); + +protected: + CTokenizer* m_tokenizer; + keywordArray_t* m_holdKeywords; +}; + +class CParsePutBack : public CParseStream +{ +public: + CParsePutBack(); + ~CParsePutBack(); + static CParsePutBack* Create(byte theByte, int curLine, LPCTSTR filename); + virtual void Delete(); + virtual bool NextChar(byte& theByte); + virtual int GetCurLine(); + virtual void GetCurFilename(char** theBuff); + virtual long GetRemainingSize(); + +protected: + virtual void Init(byte theByte, int curLine, LPCTSTR filename); + + byte m_byte; + bool m_consumed; + int m_curLine; + char* m_curFile; +}; + +class CParseMemory : public CParseStream +{ +public: + CParseMemory(); + ~CParseMemory(); + static CParseMemory* Create(byte* data, long datasize); + virtual void Delete(); + virtual bool NextChar(byte& theByte); + virtual int GetCurLine(); + virtual void GetCurFilename(char** theBuff); + virtual long GetRemainingSize(); + +protected: + virtual void Init(byte* data, long datasize); + + byte* m_data; + int m_curLine; + long m_curPos; + long m_datasize; + long m_offset; +}; + +class CParseBlock : public CParseMemory +{ +public: + CParseBlock(); + ~CParseBlock(); + static CParseBlock* Create(byte* data, long datasize); + virtual void Delete(); + +protected: + virtual void Init(byte* data, long datasize); +}; + +class CParseToken : public CParseStream +{ +public: + CParseToken(); + ~CParseToken(); + static CParseToken* Create(CToken* token); + virtual void Delete(); + virtual bool NextChar(byte& theByte); + virtual int GetCurLine(); + virtual void GetCurFilename(char** theBuff); + virtual long GetRemainingSize(); + +protected: + virtual void Init(CToken* token); + + byte* m_data; + int m_curLine; + long m_curPos; + long m_datasize; + long m_offset; +}; + +class CParseDefine : public CParseMemory +{ +public: + CParseDefine(); + ~CParseDefine(); + static CParseDefine* Create(CDirectiveSymbol* definesymbol); + virtual void Delete(); + virtual bool IsThisDefinition(void* theDefinition); + +protected: + virtual void Init(CDirectiveSymbol* definesymbol); + + CDirectiveSymbol* m_defineSymbol; +}; + +class CParseFile : public CParseStream +{ +public: + CParseFile(); + ~CParseFile(); + static CParseFile* Create(); + static CParseFile* Create(LPCTSTR filename, CTokenizer* tokenizer); +// static CParseFile* Create(CFile* file, CTokenizer* tokenizer); + virtual void Delete(); + virtual int GetCurLine(); + virtual void GetCurFilename(char** theBuff); + virtual long GetRemainingSize(); + + virtual bool NextChar(byte& theByte); + +protected: + virtual bool Init(); + virtual bool Init(LPCTSTR filename, CTokenizer* tokenizer); +// virtual void Init(CFile* file, CTokenizer* tokenizer); + DWORD GetFileSize(); + void Read(void* buff, UINT buffsize); + +// CFile* m_file; + HANDLE m_fileHandle; + char* m_fileName; + int m_curLine; + int m_curPos; + byte* m_buff; + DWORD m_curByte; + DWORD m_filesize; + bool m_ownsFile; +}; + + +#endif//__TOKENIZER_H \ No newline at end of file diff --git a/codemp/icarus/vssver.scc b/codemp/icarus/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..ee3406dec6fa04a3c8768129756d31eec3959e24 GIT binary patch literal 432 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiW+UAgWUF2Dc=5kQ))^R4|-uIGWQK#@ow|F((S zjIWko0$G6kC?G%TcG;1MR@=bx(LjF1>pPqeHN6AbfbuawzW2tq$(~_>fy_XDERdfl zZ6?_|I~1%w4y3-&A-sE0Tp$xrJ|4*DE#sQ7J|`c{PXO{a`>=+IPb~xMPXzKmnx5)Z z-BS;?KMBa^TIjG)+nqg-5vV^I$iFVOz&e`jL_*PIJr2l7*ae1~7J z&YQ~J4*or(D9mqG2KfQ$e%GUspe=~r5yUe_f&rZz&Aopbg`5kL- zyW6hp0Q0keeCuZ)n(DVs0o#`iF0Ol6}`HQ4YZ{^>%2I~g^MF@2@ literal 0 HcmV?d00001 diff --git a/codemp/install.bat b/codemp/install.bat new file mode 100644 index 0000000..95d1eb1 --- /dev/null +++ b/codemp/install.bat @@ -0,0 +1 @@ +tonet.bat \ No newline at end of file diff --git a/codemp/installvms.bat b/codemp/installvms.bat new file mode 100644 index 0000000..43c7700 --- /dev/null +++ b/codemp/installvms.bat @@ -0,0 +1,4 @@ +xcopy/d/y release\jk2mp.exe w:\game +xcopy/d/y base\vm\cgame.* w:\game\base\vm +xcopy/d/y base\vm\jk2mpgame.* w:\game\base\vm +xcopy/d/y base\vm\ui.* w:\game\base\vm diff --git a/codemp/jk2mp.vcproj b/codemp/jk2mp.vcproj new file mode 100644 index 0000000..a6c9608 --- /dev/null +++ b/codemp/jk2mp.vcproj @@ -0,0 +1,6986 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/jk2mp.vcproj.vspscc b/codemp/jk2mp.vcproj.vspscc new file mode 100644 index 0000000..794f014 --- /dev/null +++ b/codemp/jk2mp.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/codemp/jpeg-6/jcapimin.cpp b/codemp/jpeg-6/jcapimin.cpp new file mode 100644 index 0000000..f41508f --- /dev/null +++ b/codemp/jpeg-6/jcapimin.cpp @@ -0,0 +1,230 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jccoefct.cpp b/codemp/jpeg-6/jccoefct.cpp new file mode 100644 index 0000000..ca37502 --- /dev/null +++ b/codemp/jpeg-6/jccoefct.cpp @@ -0,0 +1,450 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jccolor.cpp b/codemp/jpeg-6/jccolor.cpp new file mode 100644 index 0000000..f22d36b --- /dev/null +++ b/codemp/jpeg-6/jccolor.cpp @@ -0,0 +1,461 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jcdctmgr.cpp b/codemp/jpeg-6/jcdctmgr.cpp new file mode 100644 index 0000000..20eb4ad --- /dev/null +++ b/codemp/jpeg-6/jcdctmgr.cpp @@ -0,0 +1,393 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jchuff.cpp b/codemp/jpeg-6/jchuff.cpp new file mode 100644 index 0000000..c9c8ff1 --- /dev/null +++ b/codemp/jpeg-6/jchuff.cpp @@ -0,0 +1,848 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jchuff.h b/codemp/jpeg-6/jchuff.h new file mode 100644 index 0000000..0a81d54 --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jcinit.cpp b/codemp/jpeg-6/jcinit.cpp new file mode 100644 index 0000000..16e4415 --- /dev/null +++ b/codemp/jpeg-6/jcinit.cpp @@ -0,0 +1,74 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jcmainct.cpp b/codemp/jpeg-6/jcmainct.cpp new file mode 100644 index 0000000..40885e4 --- /dev/null +++ b/codemp/jpeg-6/jcmainct.cpp @@ -0,0 +1,298 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jcmarker.cpp b/codemp/jpeg-6/jcmarker.cpp new file mode 100644 index 0000000..1d9c3be --- /dev/null +++ b/codemp/jpeg-6/jcmarker.cpp @@ -0,0 +1,641 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jcmaster.cpp b/codemp/jpeg-6/jcmaster.cpp new file mode 100644 index 0000000..b3e804e --- /dev/null +++ b/codemp/jpeg-6/jcmaster.cpp @@ -0,0 +1,580 @@ +/* + * 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). + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jcomapi.cpp b/codemp/jpeg-6/jcomapi.cpp new file mode 100644 index 0000000..799e98e --- /dev/null +++ b/codemp/jpeg-6/jcomapi.cpp @@ -0,0 +1,96 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jconfig.h b/codemp/jpeg-6/jconfig.h new file mode 100644 index 0000000..187ecfc --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jcparam.cpp b/codemp/jpeg-6/jcparam.cpp new file mode 100644 index 0000000..88fea67 --- /dev/null +++ b/codemp/jpeg-6/jcparam.cpp @@ -0,0 +1,577 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jcphuff.cpp b/codemp/jpeg-6/jcphuff.cpp new file mode 100644 index 0000000..2fadde6 --- /dev/null +++ b/codemp/jpeg-6/jcphuff.cpp @@ -0,0 +1,831 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jcprepct.cpp b/codemp/jpeg-6/jcprepct.cpp new file mode 100644 index 0000000..67eef2b --- /dev/null +++ b/codemp/jpeg-6/jcprepct.cpp @@ -0,0 +1,373 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jcsample.cpp b/codemp/jpeg-6/jcsample.cpp new file mode 100644 index 0000000..a0d3dca --- /dev/null +++ b/codemp/jpeg-6/jcsample.cpp @@ -0,0 +1,521 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jctrans.cpp b/codemp/jpeg-6/jctrans.cpp new file mode 100644 index 0000000..052836b --- /dev/null +++ b/codemp/jpeg-6/jctrans.cpp @@ -0,0 +1,373 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdapimin.cpp b/codemp/jpeg-6/jdapimin.cpp new file mode 100644 index 0000000..42eb2ce --- /dev/null +++ b/codemp/jpeg-6/jdapimin.cpp @@ -0,0 +1,400 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdapistd.cpp b/codemp/jpeg-6/jdapistd.cpp new file mode 100644 index 0000000..a3f6069 --- /dev/null +++ b/codemp/jpeg-6/jdapistd.cpp @@ -0,0 +1,277 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdatadst.cpp b/codemp/jpeg-6/jdatadst.cpp new file mode 100644 index 0000000..9a05a0d --- /dev/null +++ b/codemp/jpeg-6/jdatadst.cpp @@ -0,0 +1,153 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdatasrc.cpp b/codemp/jpeg-6/jdatasrc.cpp new file mode 100644 index 0000000..49a831d --- /dev/null +++ b/codemp/jpeg-6/jdatasrc.cpp @@ -0,0 +1,206 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdcoefct.cpp b/codemp/jpeg-6/jdcoefct.cpp new file mode 100644 index 0000000..63b6faf --- /dev/null +++ b/codemp/jpeg-6/jdcoefct.cpp @@ -0,0 +1,727 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdcolor.cpp b/codemp/jpeg-6/jdcolor.cpp new file mode 100644 index 0000000..ef87ca3 --- /dev/null +++ b/codemp/jpeg-6/jdcolor.cpp @@ -0,0 +1,369 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdct.h b/codemp/jpeg-6/jdct.h new file mode 100644 index 0000000..1d66d4f --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jddctmgr.cpp b/codemp/jpeg-6/jddctmgr.cpp new file mode 100644 index 0000000..c60b308 --- /dev/null +++ b/codemp/jpeg-6/jddctmgr.cpp @@ -0,0 +1,272 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdhuff.cpp b/codemp/jpeg-6/jdhuff.cpp new file mode 100644 index 0000000..636be18 --- /dev/null +++ b/codemp/jpeg-6/jdhuff.cpp @@ -0,0 +1,576 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdhuff.h b/codemp/jpeg-6/jdhuff.h new file mode 100644 index 0000000..65f3054 --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jdinput.cpp b/codemp/jpeg-6/jdinput.cpp new file mode 100644 index 0000000..5ec70b8 --- /dev/null +++ b/codemp/jpeg-6/jdinput.cpp @@ -0,0 +1,383 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdmainct.cpp b/codemp/jpeg-6/jdmainct.cpp new file mode 100644 index 0000000..2077750 --- /dev/null +++ b/codemp/jpeg-6/jdmainct.cpp @@ -0,0 +1,522 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdmarker.cpp b/codemp/jpeg-6/jdmarker.cpp new file mode 100644 index 0000000..17832b4 --- /dev/null +++ b/codemp/jpeg-6/jdmarker.cpp @@ -0,0 +1,1054 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdmaster.cpp b/codemp/jpeg-6/jdmaster.cpp new file mode 100644 index 0000000..8a2c858 --- /dev/null +++ b/codemp/jpeg-6/jdmaster.cpp @@ -0,0 +1,559 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdpostct.cpp b/codemp/jpeg-6/jdpostct.cpp new file mode 100644 index 0000000..7d234b6 --- /dev/null +++ b/codemp/jpeg-6/jdpostct.cpp @@ -0,0 +1,292 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdsample.cpp b/codemp/jpeg-6/jdsample.cpp new file mode 100644 index 0000000..e020d30 --- /dev/null +++ b/codemp/jpeg-6/jdsample.cpp @@ -0,0 +1,480 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jdtrans.cpp b/codemp/jpeg-6/jdtrans.cpp new file mode 100644 index 0000000..d85fc90 --- /dev/null +++ b/codemp/jpeg-6/jdtrans.cpp @@ -0,0 +1,124 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jerror.cpp b/codemp/jpeg-6/jerror.cpp new file mode 100644 index 0000000..d3cc98d --- /dev/null +++ b/codemp/jpeg-6/jerror.cpp @@ -0,0 +1,234 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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 */ + Com_Printf("%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/codemp/jpeg-6/jerror.h b/codemp/jpeg-6/jerror.h new file mode 100644 index 0000000..0ffb8b4 --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jfdctflt.cpp b/codemp/jpeg-6/jfdctflt.cpp new file mode 100644 index 0000000..650f6c1 --- /dev/null +++ b/codemp/jpeg-6/jfdctflt.cpp @@ -0,0 +1,170 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jidctflt.cpp b/codemp/jpeg-6/jidctflt.cpp new file mode 100644 index 0000000..9415d25 --- /dev/null +++ b/codemp/jpeg-6/jidctflt.cpp @@ -0,0 +1,243 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jinclude.h b/codemp/jpeg-6/jinclude.h new file mode 100644 index 0000000..99f535d --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jmemmgr.cpp b/codemp/jpeg-6/jmemmgr.cpp new file mode 100644 index 0000000..a639b31 --- /dev/null +++ b/codemp/jpeg-6/jmemmgr.cpp @@ -0,0 +1,1117 @@ +/* + * 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...) + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jmemnobs.cpp b/codemp/jpeg-6/jmemnobs.cpp new file mode 100644 index 0000000..c3b3e1b --- /dev/null +++ b/codemp/jpeg-6/jmemnobs.cpp @@ -0,0 +1,107 @@ +/* + * 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 Z_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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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 Z_Malloc() and Z_Free(). + */ + +GLOBAL void * +jpeg_get_small (j_common_ptr cinfo, size_t sizeofobject) +{ + return (void *) Z_Malloc(sizeofobject, TAG_TEMP_WORKSPACE, 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_WORKSPACE, 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/codemp/jpeg-6/jmemsys.h b/codemp/jpeg-6/jmemsys.h new file mode 100644 index 0000000..0c7d7c1 --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jmorecfg.h b/codemp/jpeg-6/jmorecfg.h new file mode 100644 index 0000000..08f332a --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jpegint.h b/codemp/jpeg-6/jpegint.h new file mode 100644 index 0000000..b3b6a6d --- /dev/null +++ b/codemp/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/codemp/jpeg-6/jpeglib.h b/codemp/jpeg-6/jpeglib.h new file mode 100644 index 0000000..dee1545 --- /dev/null +++ b/codemp/jpeg-6/jpeglib.h @@ -0,0 +1,1055 @@ +/* + * 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 + +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 + +#endif /* JPEGLIB_H */ diff --git a/codemp/jpeg-6/jutils.cpp b/codemp/jpeg-6/jutils.cpp new file mode 100644 index 0000000..ddd00f6 --- /dev/null +++ b/codemp/jpeg-6/jutils.cpp @@ -0,0 +1,177 @@ +/* + * 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. + */ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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/codemp/jpeg-6/jversion.h b/codemp/jpeg-6/jversion.h new file mode 100644 index 0000000..02083ac --- /dev/null +++ b/codemp/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/codemp/jpeg-6/vssver.scc b/codemp/jpeg-6/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..7cd142b6b9e397b624bd2c7719a8b47477f2ed9d GIT binary patch literal 784 zcmXBSdq`7Z7zXe&Z91#DHJz8N<;W~$A(n(JOQ&nFM5Y^Ez(r*lL78l2&=_7yx@gjs zL_}d3wz7$uGXF@8MvXLWCDCPSRM1Qjmd!S2%O2kjeBXJ0JnwnWg_9;=F=w9(ifh03 z%I}RQXhny;<^u2jky+;q~#=am_s zX_nrH1+RZt+d4k=k{kqQN#w>{=?8K!TqkNz+1lbFFN1ZD*xJUV88RD=ovCmSG%k`u z;KT!_2F z{_W`wLqFv!VOGsaQ-xwTnG1`z4J5l>6;OXT?6^ASQ`ngqx+i-BITDtLtarTaTgXBku*-`HbWPg%#$al)B1k>R$~jhnh1? zdJpAsu%ypcV-*I}OY_&jZFeozmpC`6e=VGJ%okDG*+3S-#ishC_nG&}>tKVSv}F26 cCtV*8Ta~Qa*9x;~z8H?*JDJv0luP~p027_dVE_OC literal 0 HcmV?d00001 diff --git a/codemp/mp3code/cdct.c b/codemp/mp3code/cdct.c new file mode 100644 index 0000000..556270f --- /dev/null +++ b/codemp/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/codemp/mp3code/config.h b/codemp/mp3code/config.h new file mode 100644 index 0000000..a5da233 --- /dev/null +++ b/codemp/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/codemp/mp3code/copyright.h b/codemp/mp3code/copyright.h new file mode 100644 index 0000000..5f75427 --- /dev/null +++ b/codemp/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/codemp/mp3code/csbt.c b/codemp/mp3code/csbt.c new file mode 100644 index 0000000..a88d338 --- /dev/null +++ b/codemp/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/codemp/mp3code/csbtb.c b/codemp/mp3code/csbtb.c new file mode 100644 index 0000000..48be054 --- /dev/null +++ b/codemp/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/codemp/mp3code/csbtl3.c b/codemp/mp3code/csbtl3.c new file mode 100644 index 0000000..6e9537d --- /dev/null +++ b/codemp/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/codemp/mp3code/cup.c b/codemp/mp3code/cup.c new file mode 100644 index 0000000..6277d4b --- /dev/null +++ b/codemp/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/codemp/mp3code/cupini.c b/codemp/mp3code/cupini.c new file mode 100644 index 0000000..4a7d4c9 --- /dev/null +++ b/codemp/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/codemp/mp3code/cupl1.c b/codemp/mp3code/cupl1.c new file mode 100644 index 0000000..f3b664d --- /dev/null +++ b/codemp/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/codemp/mp3code/cupl3.c b/codemp/mp3code/cupl3.c new file mode 100644 index 0000000..e3cba5b --- /dev/null +++ b/codemp/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/codemp/mp3code/cwin.c b/codemp/mp3code/cwin.c new file mode 100644 index 0000000..884abb1 --- /dev/null +++ b/codemp/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/codemp/mp3code/cwinb.c b/codemp/mp3code/cwinb.c new file mode 100644 index 0000000..537a4b7 --- /dev/null +++ b/codemp/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/codemp/mp3code/cwinm.c b/codemp/mp3code/cwinm.c new file mode 100644 index 0000000..8a18f0d --- /dev/null +++ b/codemp/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/codemp/mp3code/htable.h b/codemp/mp3code/htable.h new file mode 100644 index 0000000..3131bd5 --- /dev/null +++ b/codemp/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/codemp/mp3code/hwin.c b/codemp/mp3code/hwin.c new file mode 100644 index 0000000..26d7dd8 --- /dev/null +++ b/codemp/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/codemp/mp3code/jdw.h b/codemp/mp3code/jdw.h new file mode 100644 index 0000000..c8751f9 --- /dev/null +++ b/codemp/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/codemp/mp3code/l3.h b/codemp/mp3code/l3.h new file mode 100644 index 0000000..f7cfee4 --- /dev/null +++ b/codemp/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/codemp/mp3code/l3dq.c b/codemp/mp3code/l3dq.c new file mode 100644 index 0000000..dda1881 --- /dev/null +++ b/codemp/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/codemp/mp3code/l3init.c b/codemp/mp3code/l3init.c new file mode 100644 index 0000000..aaf1ad7 --- /dev/null +++ b/codemp/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/codemp/mp3code/mdct.c b/codemp/mp3code/mdct.c new file mode 100644 index 0000000..10c21cf --- /dev/null +++ b/codemp/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/codemp/mp3code/mhead.c b/codemp/mp3code/mhead.c new file mode 100644 index 0000000..496ff4a --- /dev/null +++ b/codemp/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/codemp/mp3code/mhead.h b/codemp/mp3code/mhead.h new file mode 100644 index 0000000..3a526b1 --- /dev/null +++ b/codemp/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/codemp/mp3code/mp3struct.h b/codemp/mp3code/mp3struct.h new file mode 100644 index 0000000..74299a5 --- /dev/null +++ b/codemp/mp3code/mp3struct.h @@ -0,0 +1,140 @@ +// 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; + + +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/codemp/mp3code/msis.c b/codemp/mp3code/msis.c new file mode 100644 index 0000000..6e7ef24 --- /dev/null +++ b/codemp/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/codemp/mp3code/port.h b/codemp/mp3code/port.h new file mode 100644 index 0000000..78ad088 --- /dev/null +++ b/codemp/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/codemp/mp3code/small_header.h b/codemp/mp3code/small_header.h new file mode 100644 index 0000000..bfbe7cf --- /dev/null +++ b/codemp/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/codemp/mp3code/tableawd.h b/codemp/mp3code/tableawd.h new file mode 100644 index 0000000..4f08dd7 --- /dev/null +++ b/codemp/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/codemp/mp3code/towave.c b/codemp/mp3code/towave.c new file mode 100644 index 0000000..9f3f717 --- /dev/null +++ b/codemp/mp3code/towave.c @@ -0,0 +1,760 @@ +/*____________________________________________________________________________ + + 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 + // stuff 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); +//#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) //3 seems to mean mono + { + if (iDataLen > 98000) { // we'll allow it for small files even if stereo + 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 = 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) +// { +// 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 +// +// NEW CODE NOTE: Now that this function is being called for raw data that's going to be runtime-stored in a link-list +// chunk format for SOF2 instead of just one alloc then you must re-init pMP3Stream->pbSourceData after you've called +// this, or it'll be pointing at the deallocated original raw data, not the first chunk of the link list +// +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; // this MUST be re-initialised to link-mem outside here for SOF2, since raw data is now link-listed + 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 ) +{ + 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; + } + + x = audio.decode(pSFX_MP3Stream->pbSourceData + pSFX_MP3Stream->iSourceReadIndex, (short *) (pSFX_MP3Stream->bDecodeBuffer), + pSFX_MP3Stream->pbSourceData + pSFX_MP3Stream->iRewind_SourceReadIndex + pSFX_MP3Stream->iRewind_SourceBytesRemaining + ); + +#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/codemp/mp3code/uph.c b/codemp/mp3code/uph.c new file mode 100644 index 0000000..18f9e88 --- /dev/null +++ b/codemp/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/codemp/mp3code/upsf.c b/codemp/mp3code/upsf.c new file mode 100644 index 0000000..e538229 --- /dev/null +++ b/codemp/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/codemp/mp3code/vssver.scc b/codemp/mp3code/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..8a610cc5edc1d1fc842cc0fa3614e218e289432a GIT binary patch literal 528 zcmW;ITPVYE6bJDC3?;>y_96{4W^)^c&_r&Fq-Jy5n13OQ+Ws^z%Voa>eP zBk-lbPb1!@q7M|(P>Tyuq`*@CzB?6re zwo~_ijgc{EKA4@S5f{y}&;qcjenETqk%i6xM|qPjetpI0OmIbfgr=W;zjmNd**=Yj*>yXuQe6FLvPs8h2mD=g@I z@Tr}uv+OOSMc_1^>s9AkLl=OloFBqnZbKJ>o82qNZD+OUBCx@^HF2HXfDSF;oFHec zHW|7J{E{+gy`q$88JIjjO)oGEphMC3+}l;UtwT41tBwrjJM9R%1#GTi+4h6s=vMHb sEcofWVaVxlpEmH^m&zf2Opu3x+ri`f66R*70Ou88)@b7J4w;Ys2gOZ +#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/codemp/mssccprj.scc b/codemp/mssccprj.scc new file mode 100644 index 0000000..34e1057 --- /dev/null +++ b/codemp/mssccprj.scc @@ -0,0 +1,17 @@ +SCC = This is a Source Code Control file + +[jk2mp.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp", CAAAAAAA + +[JKA_mp.sln] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp", CAAAAAAA + +[WinDed.dsp] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp", CAAAAAAA + +[WinDed.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp", CAAAAAAA diff --git a/codemp/namespace_begin.h b/codemp/namespace_begin.h new file mode 100644 index 0000000..1a76e25 --- /dev/null +++ b/codemp/namespace_begin.h @@ -0,0 +1,15 @@ +#ifdef _XBOX + +#ifdef _GAME +namespace game { +#elif defined _CGAME +namespace cgame { +#elif defined _UI +namespace ui { +#else +// Hmmm. Some of the module headers end up included in EXE code. +// Let's hope that the apocalpyse can be held at bay. +// #error Including namespace_begin.h, but not in UI/GAME/CGAME +#endif + +#endif // _XBOX \ No newline at end of file diff --git a/codemp/namespace_end.h b/codemp/namespace_end.h new file mode 100644 index 0000000..b176307 --- /dev/null +++ b/codemp/namespace_end.h @@ -0,0 +1,17 @@ +#ifdef _XBOX + +#ifdef _GAME +} +using namespace game; +#elif defined _CGAME +} +using namespace cgame; +#elif defined _UI +} +using namespace ui; +#else +// See comment int namespace_begin.h +//#error Including namespace_end.h, but not in UI/GAME/CGAME +#endif + +#endif _XBOX \ No newline at end of file diff --git a/codemp/null/mac_net.c b/codemp/null/mac_net.c new file mode 100644 index 0000000..6232aff --- /dev/null +++ b/codemp/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/codemp/null/null_client.cpp b/codemp/null/null_client.cpp new file mode 100644 index 0000000..421efb9 --- /dev/null +++ b/codemp/null/null_client.cpp @@ -0,0 +1,68 @@ + +#include "../client/client.h" + + +char cl_cdkey[17] = "123456789"; + +cvar_t *cl_shownet; + +void CL_Shutdown( void ) { +} + +void CL_Init( void ) { + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); +} + +void CL_MouseEvent( int dx, int dy, int time ) { +} + +void Key_WriteBindings( fileHandle_t f ) { +} + +void CL_Frame ( int msec ) { +} + +void CL_PacketEvent( netadr_t from, msg_t *msg ) { +} + +void CL_CharEvent( int key ) { +} + +void CL_Disconnect( qboolean showMainMenu ) { +} + +void CL_MapLoading( void ) { +} + +qboolean CL_GameCommand( void ) { + return qfalse; +} + +void CL_KeyEvent (int key, qboolean down, unsigned time) { +} + +qboolean UI_GameCommand( void ) { + return qfalse; +} + +void CL_ForwardCommandToServer( const char *string ) { +} + +void CL_ConsolePrint( const char *txt, qboolean silent ) { +} + +void CL_JoystickEvent( int axis, int value, int time ) { +} + +void CL_InitKeyCommands( void ) { +} + +void CL_CDDialog( const char *msg ) { +} + +void CL_FlushMemory( void ) { +} + +void CL_StartHunkUsers( void ) { +} + diff --git a/codemp/null/null_glimp.cpp b/codemp/null/null_glimp.cpp new file mode 100644 index 0000000..0c59867 --- /dev/null +++ b/codemp/null/null_glimp.cpp @@ -0,0 +1,74 @@ +#include "../game/q_shared.h" +#include "../renderer/tr_local.h" +#ifdef __linux__ +typedef unsigned int GLenum; +#endif + +#ifdef _WIN32 +#include +BOOL (WINAPI * qwglSwapIntervalEXT)( int interval ); +//void (APIENTRY * qglMultiTexCoord2fARB )( GLenum texture, float s, float t ); +//void (APIENTRY * qglActiveTextureARB )( GLenum texture ); +//void (APIENTRY * qglClientActiveTextureARB )( GLenum texture ); + + +void (APIENTRY * qglLockArraysEXT)( int, int); +void (APIENTRY * qglUnlockArraysEXT) ( void ); + + +void GLimp_EndFrame( void ) { +} + +void GLimp_Init( void ) +{ +} + +void GLimp_Shutdown( void ) { +} + +void GLimp_EnableLogging( qboolean enable ) { +} + +void GLimp_LogComment( char *comment ) { +} + +qboolean QGL_Init( const char *dllname ) { + return qtrue; +} + +void QGL_Shutdown( void ) { +} +#else +qboolean ( * qwglSwapIntervalEXT)( int interval ); +void ( * qglMultiTexCoord2fARB )( GLenum texture, float s, float t ); +void ( * qglActiveTextureARB )( GLenum texture ); +void ( * qglClientActiveTextureARB )( GLenum texture ); + + +void ( * qglLockArraysEXT)( int, int); +void ( * qglUnlockArraysEXT) ( void ); + + +void GLimp_EndFrame( void ) { +} + +void GLimp_Init( void ) +{ +} + +void GLimp_Shutdown( void ) { +} + +void GLimp_EnableLogging( qboolean enable ) { +} + +void GLimp_LogComment( char *comment ) { +} + +qboolean QGL_Init( const char *dllname ) { + return qtrue; +} + +void QGL_Shutdown( void ) { +} +#endif // !WIN32 diff --git a/codemp/null/null_input.cpp b/codemp/null/null_input.cpp new file mode 100644 index 0000000..7f79d2f --- /dev/null +++ b/codemp/null/null_input.cpp @@ -0,0 +1,14 @@ +#include "../client/client.h" + +void IN_Init( void ) { +} + +void IN_Frame (void) { +} + +void IN_Shutdown( void ) { +} + +void Sys_SendKeyEvents (void) { +} + diff --git a/codemp/null/null_main.c b/codemp/null/null_main.c new file mode 100644 index 0000000..cab5b0d --- /dev/null +++ b/codemp/null/null_main.c @@ -0,0 +1,95 @@ +// sys_null.h -- null system driver to aid porting efforts + +#include +#include +#include "../qcommon/qcommon.h" + +int sys_curtime; + + +//=================================================================== + +void Sys_BeginStreamedFile( FILE *f, int readAhead ) { +} + +void Sys_EndStreamedFile( FILE *f ) { +} + +int Sys_StreamedRead( void *buffer, int size, int count, FILE *f ) { + return fread( buffer, size, count, f ); +} + +void Sys_StreamSeek( FILE *f, int offset, int origin ) { + fseek( f, offset, origin ); +} + + +//=================================================================== + + +void Sys_mkdir ( const 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/codemp/null/null_net.c b/codemp/null/null_net.c new file mode 100644 index 0000000..2973a8d --- /dev/null +++ b/codemp/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/codemp/null/null_renderer.cpp b/codemp/null/null_renderer.cpp new file mode 100644 index 0000000..dadb28c --- /dev/null +++ b/codemp/null/null_renderer.cpp @@ -0,0 +1,21 @@ +/* Null renderer functions */ + +void RB_StageIteratorGeneric(void) +{ +} + +void RB_StageIteratorSky(void) +{ +} + +void RB_StageIteratorVertexLitTexture(void) +{ +} + +void RB_StageIteratorLightmappedMultitexture(void) +{ +} + +void R_SyncRenderThread(void) +{ +} diff --git a/codemp/null/null_snddma.cpp b/codemp/null/null_snddma.cpp new file mode 100644 index 0000000..eb2afce --- /dev/null +++ b/codemp/null/null_snddma.cpp @@ -0,0 +1,49 @@ + +// snddma_null.c +// all other sound mixing is portable + +#include "../client/client.h" + +qboolean gbInsideLoadSound = qfalse; // important to default to this!!! + +qboolean SNDDMA_Init(void) +{ + return qfalse; +} + +int SNDDMA_GetDMAPos(void) +{ + return 0; +} + +void SNDDMA_Shutdown(void) +{ +} + +void SNDDMA_BeginPainting (void) +{ +} + +void SNDDMA_Submit(void) +{ +} + +sfxHandle_t S_RegisterSound( const char *name ) { + return 0; +} + +void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { +} + +void S_ClearSoundBuffer( void ) { +} + +qboolean SND_RegisterAudio_LevelLoadEnd(qboolean something) +{ + return qfalse; +} + +int SND_FreeOldestSound(void) +{ + return 0; +} \ No newline at end of file diff --git a/codemp/null/vssver.scc b/codemp/null/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..7a0242130fd313318ee74f72db2625b416825fd8 GIT binary patch literal 176 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiXr-HvUGVPpV +#include +#include +#include +#include +#include +#include +#include "../qcommon/stringed_ingame.h" + +#define CD_BASEDIR "gamedata\\gamedata" +#define CD_EXE "jamp.exe" +#define CD_BASEDIR_LINUX "bin\\x86\\glibc-2.1" +#define CD_EXE_LINUX "jamp" +#define CD_VOLUME "JEDIACAD" +#define MEM_THRESHOLD 128*1024*1024 + +static char sys_cmdline[MAX_STRING_CHARS]; +clientStatic_t cls; + +static int sys_monkeySpank; +static int sys_checksum; + + +void *Sys_GetBotAIAPI (void *parms ) { + return NULL; +} + +void Conbuf_AppendText( const char *pMsg ) +{ + char msg[4096]; + strcpy(msg, pMsg); + printf(Q_CleanStr(msg)); + printf("\n"); +} + +/* +================== +Sys_LowPhysicalMemory() +================== +*/ + +qboolean Sys_LowPhysicalMemory() { + MEMORYSTATUS stat; + GlobalMemoryStatus (&stat); + return (stat.dwTotalPhys <= MEM_THRESHOLD) ? qtrue : qfalse; +} + +/* +================== +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; + 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); + return Com_BlockChecksum( f1_ptr, l ); +} + +/* +================== +Sys_MonkeyShouldBeSpanked +================== +*/ +int Sys_MonkeyShouldBeSpanked( void ) { + return sys_monkeySpank; +} + + +/* +================== +Sys_VerifyCodeChecksum +================== +*/ +void Sys_VerifyCodeChecksum( void *codeBase ) { +} + +/* +=============== +PrintMatches + +=============== +*/ +static char g_consoleField1[256]; +static char g_consoleField2[256]; + +static void PrintMatches( const char *s ) { + if ( !Q_stricmpn( s, g_consoleField1, strlen( g_consoleField1 ) ) ) { + printf( " %s\n", s ); + } +} + +//qboolean stdin_active = qtrue; +char *Sys_ConsoleInput(void) +{ + const char ClearLine[] = "\r \r"; + + static int len=0; + static bool bPendingExtended = false; + + if (!kbhit()) return NULL; + + if (len == 0) memset(g_consoleField1,0,sizeof(g_consoleField1)); + + g_consoleField1[len] = getch(); + + if (bPendingExtended) + { + switch (g_consoleField1[len]) + { + case 'H': //up + strcpy(g_consoleField1, g_consoleField2); + printf(ClearLine); + printf("%s",g_consoleField1); + len = strlen(g_consoleField1); + break; + + case 'K': //left + break; + + case 'M': //right + break; + + case 'P': //down + break; + } + g_consoleField1[len] = 0; //erase last key hit + bPendingExtended = false; + } + else + switch ((unsigned char) g_consoleField1[len]) + { + case 0x00: //fkey is next + case 0xe0: //extended = arrow keys + g_consoleField1[len] = 0; //erase last key hit + bPendingExtended = true; + break; + case 8: // backspace + printf("%c %c",g_consoleField1[len],g_consoleField1[len]); + g_consoleField1[len] = 0; + if (len > 0) len--; + g_consoleField1[len] = 0; + break; + case 9: //Tab + if (len) { + g_consoleField1[len] = 0; //erase last key hit + printf( "\n"); + // run through again, printing matches + Cmd_CommandCompletion( PrintMatches ); + Cvar_CommandCompletion( PrintMatches ); + printf( "\n%s", g_consoleField1); + } + break; + case 27: // esc + // clear the line + printf(ClearLine); + len = 0; + break; + case '\r': //enter + g_consoleField1[len] = 0; //erase last key hit + printf("\n"); + if (len) { + len = 0; + strcpy(g_consoleField2, g_consoleField1); + return g_consoleField1; + } + break; + case 'v' - 'a' + 1: // ctrl-v is paste + g_consoleField1[len] = 0; //erase last key hit + char *cbd; + cbd = Sys_GetClipboardData(); + if (cbd) { + strncpy (&g_consoleField1[len], cbd, sizeof(g_consoleField1) ); + printf("%s",cbd); + len += strlen(cbd); + Z_Free( cbd ); + if (len == sizeof(g_consoleField1)) + { + len = 0; + return g_consoleField1; + } + } + break; + default: + printf("%c",g_consoleField1[len]); + len++; + if (len == sizeof(g_consoleField1)) + { + len = 0; + return g_consoleField1; + } + break; + } + + return NULL; +} + +/* +================== +Sys_BeginProfiling +================== +*/ +void Sys_BeginProfiling( void ) { + // this is just used on the mac build +} + +void Sys_ShowConsole( int visLevel, qboolean quitOnClose ) +{ +} + +/* +============= +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 + +void Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **psList, int *numfiles ) { + char search[MAX_OSPATH], newsubdirs[MAX_OSPATH]; + char filename[MAX_OSPATH]; + int findhandle; + struct _finddata_t findinfo; + + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + return; + } + + if (strlen(subdirs)) { + Com_sprintf( search, sizeof(search), "%s\\%s\\*", basedir, subdirs ); + } + else { + Com_sprintf( search, sizeof(search), "%s\\*", basedir ); + } + + findhandle = _findfirst (search, &findinfo); + if (findhandle == -1) { + return; + } + + do { + if (findinfo.attrib & _A_SUBDIR) { + if (Q_stricmp(findinfo.name, ".") && Q_stricmp(findinfo.name, "..")) { + if (strlen(subdirs)) { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s\\%s", subdirs, findinfo.name); + } + else { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", findinfo.name); + } + Sys_ListFilteredFiles( basedir, newsubdirs, filter, psList, numfiles ); + } + } + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + break; + } + Com_sprintf( filename, sizeof(filename), "%s\\%s", subdirs, findinfo.name ); + if (!Com_FilterPath( filter, filename, qfalse )) + continue; + psList[ *numfiles ] = CopyString( filename ); + (*numfiles)++; + } while ( _findnext (findhandle, &findinfo) != -1 ); + + _findclose (findhandle); +} + +static qboolean strgtr(const char *s0, const char *s1) { + int l0, l1, i; + + l0 = strlen(s0); + l1 = strlen(s1); + + if (l1 s0[i]) { + return qtrue; + } + if (s1[i] < s0[i]) { + return qfalse; + } + } + return qfalse; +} + +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, 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 (filter) { + + nfiles = 0; + Sys_ListFilteredFiles( directory, "", filter, list, &nfiles ); + + list[ nfiles ] = 0; + *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; + } + + 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_FILESYS ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + do { + flag = 0; + for(i=1; i (5 * 60000)) && !Cvar_VariableIntegerValue( "dedicated" ) +// && !Cvar_VariableIntegerValue( "com_blindlyLoadDLLs" ) ) { + if (0) { + if (FS_FileExists(filename)) { + lastWarning = timestamp; + ret = MessageBoxEx( NULL, "You are about to load a .DLL executable that\n" + "has not been verified for use with Quake III Arena.\n" + "This type of file can compromise the security of\n" + "your computer.\n\n" + "Select 'OK' if you choose to load it anyway.", + "Security Warning", MB_OKCANCEL | MB_ICONEXCLAMATION | MB_DEFBUTTON2 | MB_TOPMOST | MB_SETFOREGROUND, + MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ) ); + if( ret != IDOK ) { + return NULL; + } + } + } +#endif + + +// rjr disable for final release #ifndef NDEBUG + libHandle = LoadLibrary( filename ); + if ( !libHandle ) { +//#endif + basepath = Cvar_VariableString( "fs_basepath" ); + cdpath = Cvar_VariableString( "fs_cdpath" ); + gamedir = Cvar_VariableString( "fs_game" ); + + fn = FS_BuildOSPath( basepath, gamedir, filename ); + libHandle = LoadLibrary( fn ); + + if ( !libHandle ) { + if( cdpath[0] ) { + fn = FS_BuildOSPath( cdpath, gamedir, filename ); + libHandle = LoadLibrary( fn ); + } + + if ( !libHandle ) { + return NULL; + } + } +//#ifndef NDEBUG + } +//#endif + + dllEntry = ( void (QDECL *)( int (QDECL *)( int, ... ) ) )GetProcAddress( libHandle, "dllEntry" ); + *entryPoint = (int (QDECL *)(int,...))GetProcAddress( libHandle, "vmMain" ); + if ( !*entryPoint || !dllEntry ) { + FreeLibrary( libHandle ); + return NULL; + } + dllEntry( systemcalls ); + + return libHandle; +} + + +/* +======================================================================== + +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 { + fileHandle_t file; + byte *buffer; + qboolean eof; + qboolean active; + int bufferSize; + int streamPosition; // next byte to be returned by Sys_StreamRead + int threadPosition; // next byte to be read from file +} streamsIO_t; + +typedef struct { + HANDLE threadHandle; + int threadId; + CRITICAL_SECTION crit; + streamsIO_t sIO[MAX_FILE_HANDLES]; +} 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, i; + + while (1) { + Sleep( 10 ); +// EnterCriticalSection (&stream.crit); + + for (i=1;i 0 ) { + available = stream.sIO[f].threadPosition - stream.sIO[f].streamPosition; + if ( !available ) { + if ( stream.sIO[f].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; + } + + EnterCriticalSection( &stream.crit ); + + bufferPoint = stream.sIO[f].streamPosition % stream.sIO[f].bufferSize; + bufferCount = stream.sIO[f].bufferSize - bufferPoint; + + copy = available < bufferCount ? available : bufferCount; + if ( copy > remaining ) { + copy = remaining; + } + memcpy( dest, stream.sIO[f].buffer + bufferPoint, copy ); + stream.sIO[f].streamPosition += copy; + dest += copy; + remaining -= copy; + + LeaveCriticalSection( &stream.crit ); + } + + 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.sIO[f].streamPosition = 0; + stream.sIO[f].threadPosition = 0; + stream.sIO[f].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]; +static int eventHead=0; +static int eventTail=0; +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; + netadr_t adr; + + // 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 ); + Q_strncpyz( b, s, len ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + // 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 + // the readcount stepahead is for SOCKS support + len = sizeof( netadr_t ) + netmsg.cursize - netmsg.readcount; + buf = (netadr_t *)Z_Malloc( len, TAG_EVENT, qtrue ); + *buf = adr; + memcpy( buf+1, &netmsg.data[netmsg.readcount], netmsg.cursize - netmsg.readcount ); + 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 = timeGetTime(); + + return ev; +} + +//================================================================ + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( void ) { + IN_Shutdown(); + IN_Init(); +} + + +/* +================= +Sys_Net_Restart_f + +Restart the network subsystem +================= +*/ +void Sys_Net_Restart_f( void ) { + NET_Restart(); +} + + +/* +================ +Sys_Init + +Called after the common systems (cvars, files, etc) +are initialized +================ +*/ +#define OSR2_BUILD_NUMBER 1111 +#define WIN98_BUILD_NUMBER 1998 + +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); + Cmd_AddCommand ("net_restart", Sys_Net_Restart_f); + +// g_wv.osversion.dwOSVersionInfoSize = sizeof( g_wv.osversion ); + +// if (!GetVersionEx (&g_wv.osversion)) +// Sys_Error ("Couldn't get OS info"); + +// 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", 0 ); + 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? +} + + +//======================================================================= +//int totalMsec, countMsec; + +/* +================== +WinMain + +================== +*/ +//int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { +int main(int argc, char **argv) +{ + char cwd[MAX_OSPATH]; + char *cmdline; + int i,len; +// int startTime, endTime; + + // should never get a previous instance in Win32 +// if ( hPrevInstance ) { +// return 0; +// } + +// sys_checksum = Sys_CodeInMemoryChecksum( hInstance ); +// Sys_VerifyCodeChecksum( hInstance ); + + // merge the command line, this is kinda silly + for (len = 1, i = 1; i < argc; i++) + len += strlen(argv[i]) + 1; + cmdline = (char *)malloc(len); + *cmdline = 0; + for (i = 1; i < argc; i++) { + if (i > 1) + strcat(cmdline, " "); + strcat(cmdline, argv[i]); + } + +// 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( cmdline ); + + NET_Init(); + + _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_dedicated->integer && !com_viewlog->integer ) { + Sys_ShowConsole( 0, qfalse ); + } + +#ifdef _DEBUG + if ( sys_monkeySpank ) { + Cvar_Set("cl_trn", "666"); + } +#endif + + // main game loop + while( 1 ) { + // if not running as a game client, sleep a bit +// if ( g_wv.isMinimized || ( com_dedicated && com_dedicated->integer ) ) { + Sleep( 5 ); +// } + + // 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 + return 0; +} + + diff --git a/codemp/png/png.cpp b/codemp/png/png.cpp new file mode 100644 index 0000000..66d4a47 --- /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..7a3580a --- /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/png/vssver.scc b/codemp/png/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..5b6ff2131518927970711c3c7a2af1fac089d49b GIT binary patch literal 64 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiYW{dpSfCddE=7X=s?9Dja0{IX|%2pdr35|D4y O_pP$G&Le~o$OixxycV(m literal 0 HcmV?d00001 diff --git a/codemp/qcommon/CNetProfile.cpp b/codemp/qcommon/CNetProfile.cpp new file mode 100644 index 0000000..c0de3a3 --- /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/GenericParser2.cpp b/codemp/qcommon/GenericParser2.cpp new file mode 100644 index 0000000..da3775a --- /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..3f0ca3c --- /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/INetProfile.h b/codemp/qcommon/INetProfile.h new file mode 100644 index 0000000..9dd9600 --- /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/MiniHeap.h b/codemp/qcommon/MiniHeap.h new file mode 100644 index 0000000..711f5a4 --- /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/RoffSystem.cpp b/codemp/qcommon/RoffSystem.cpp new file mode 100644 index 0000000..b88863a --- /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..dbfe1f0 --- /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/chash.h b/codemp/qcommon/chash.h new file mode 100644 index 0000000..b9f5cb1 --- /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..80db6bd --- /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..a093ef1 --- /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..872171f --- /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..a199356 --- /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..c3f05c9 --- /dev/null +++ b/codemp/qcommon/cm_load_xbox.cpp @@ -0,0 +1,1127 @@ +// 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 "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, 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; + } +} + +/* +================= +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..f97a97e --- /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..48f4847 --- /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..486ed21 --- /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..c86ff8e --- /dev/null +++ b/codemp/qcommon/cm_patch_xbox.cpp @@ -0,0 +1,1782 @@ +//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 = 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 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, 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; +} + +/* +================== +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 inline 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 inline 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 +================== +*/ +inline 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 inline 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 +==================== +*/ +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; // 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 +==================== +*/ +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->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 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] ]; + planes = &pc->planes[ facet->GetBorderPlanes()[j] ]; +// if (facet->borderInward[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 ); + } + 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) ) { + +} \ 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..61f2526 --- /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..d010769 --- /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..6f925d4 --- /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..7fc5951 --- /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..4f97432 --- /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..d4597c0 --- /dev/null +++ b/codemp/qcommon/cm_shader.cpp @@ -0,0 +1,538 @@ +//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; +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 ) +{ + char **shaderFiles1; + int numShaders1; +#ifndef FINAL_BUILD + char **shaderFiles2; + int numShaders2; +#endif + char *buffers[MAX_SHADER_FILES]; + int numShaders; + int i; + int sum = 0; + + // scan for shader files + shaderFiles1 = FS_ListFiles( "shaders", ".shader", &numShaders1 ); +#ifndef FINAL_BUILD + shaderFiles2 = FS_ListFiles( "shaders/test", ".shader", &numShaders2 ); +#endif + + if ( !shaderFiles1 || !numShaders1 ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: no shader files found\n" ); + return; + } + +#ifndef FINAL_BUILD + numShaders = numShaders1 + numShaders2; +#else + numShaders = numShaders1; +#endif + 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 ); + sum += FS_ReadFile( filename, (void **)&buffers[i] ); + if ( !buffers[i] ) + { + Com_Error( ERR_FATAL, "Couldn't load %s", filename ); + } + } +#ifndef FINAL_BUILD + for ( ; i < numShaders; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "shaders/test/%s", shaderFiles2[i - numShaders1] ); + Com_DPrintf( "...loading '%s'\n", filename ); + sum += FS_ReadFile( filename, (void **)&buffers[i] ); + if ( !buffers[i] ) + { + Com_Error( ERR_DROP, "Couldn't load %s", filename ); + } + } +#endif + + // 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 ); +#ifndef FINAL_BUILD + FS_FreeFileList( shaderFiles2 ); +#endif +} + +/* +================== +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(qboolean 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 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, "(" ) ) + { + 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. +================= +*/ +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..884b91c --- /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..11c3405 --- /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..62cf9b8 --- /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..7cd325d --- /dev/null +++ b/codemp/qcommon/cm_test.cpp @@ -0,0 +1,573 @@ +//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..f0e28e2 --- /dev/null +++ b/codemp/qcommon/cm_trace.cpp @@ -0,0 +1,1992 @@ +//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..a4980f9 --- /dev/null +++ b/codemp/qcommon/cmd_common.cpp @@ -0,0 +1,508 @@ +// cmd.c -- Quake script command processing module + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + + +#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 ) { + if ( Cmd_Argc() == 2 ) { + cmd_wait = atoi( Cmd_Argv( 1 ) ); + } else { + cmd_wait = 1; + } +} + + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ + +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init (void) +{ + cmd_text.data = cmd_text_buf; + cmd_text.maxsize = MAX_CMD_BUFFER; + cmd_text.cursize = 0; +} + +/* +============ +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; + } + Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l); + cmd_text.cursize += l; +} + + +/* +============ +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 + Com_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: + 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; + + 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); + } +} + + +/* +============================================================================== + + 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-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..5088e91 --- /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..b93be6f --- /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/common.cpp b/codemp/qcommon/common.cpp new file mode 100644 index 0000000..60c57e9 --- /dev/null +++ b/codemp/qcommon/common.cpp @@ -0,0 +1,2236 @@ +// 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" +#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 + +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; +} + +/* +============= +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, ... ) { + 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 +} + + +/* +================ +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. +============= +*/ +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 + +#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); + + 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 +// bk001129 - init, also static +static int com_pushedEventsHead = 0; +static int com_pushedEventsTail = 0; +// bk001129 - static +static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; + +/* +================= +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 + com_pushedEventsHead = 0; + com_pushedEventsTail = 0; +} + + +/* +================= +Com_PushEvent +================= +*/ +void Com_PushEvent( sysEvent_t *event ) { + sysEvent_t *ev; + static int printedWarning = 0; // bk001129 - init, bk001204 - explicit int + + 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: + // 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; + } + + // 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 + { + // 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 + extern void WF_Init(); + WF_Init(); +#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 (); + + // 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 { + 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 + 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 (); + } + Com_EventLoop(); + Cbuf_Execute (); + + + // + // client side + // + if ( com_speeds->integer ) { + timeBeforeClient = Sys_Milliseconds (); + } + + CL_Frame( msec ); + + 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 ("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 + 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 ((!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..da08f69 --- /dev/null +++ b/codemp/qcommon/cvar.cpp @@ -0,0 +1,1019 @@ +// cvar.c -- dynamic variable tracking + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.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 + 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) + 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..971ed5e --- /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..e3f18c6 --- /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..2655b29 --- /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.cpp b/codemp/qcommon/files.cpp new file mode 100644 index 0000000..fc54025 --- /dev/null +++ b/codemp/qcommon/files.cpp @@ -0,0 +1,3614 @@ +/***************************************************************************** + * name: files.c + * + * desc: handle based filesystem for Quake III Arena + * + * $Archive: /MissionPack/code/qcommon/files.c $ + * $Author: Zaphod $ + * $Revision: 81 $ + * $Modtime: 5/30/01 2:31p $ + * $Date: 5/30/01 2:31p $ + * + *****************************************************************************/ + +//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 //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 jk2mpconfig.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 jk2mpconfig.cfg isn't present in it, +or configs will never get loaded from disk! + + todo: + + downloading (outside fs?) + game directory passing and restarting + +============================================================================= + +*/ + +#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 + unzFile handle; // handle to zip file + 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; + +static char fs_gamedir[MAX_OSPATH]; // this will be a single file name with no separators +static cvar_t *fs_debug; +static cvar_t *fs_homepath; +static cvar_t *fs_basepath; +static cvar_t *fs_basegame; +static cvar_t *fs_cdpath; +static cvar_t *fs_copyfiles; +/*static*/ cvar_t *fs_gamedirvar; +static cvar_t *fs_restrict; +static cvar_t *fs_dirbeforepak; //rww - when building search path, keep directories at top and insert pk3's under them +static searchpath_t *fs_searchpaths; +static int fs_readCount; // total bytes read +static int fs_loadCount; // total files read +static int fs_loadStack; // total files in memory +static int fs_packFiles; // total number of files in packs + +static int fs_fakeChkSum; +static int fs_checksumFeed; + +typedef union qfile_gus { + FILE* o; + unzFile z; +} 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]; +} fileHandleData_t; + +static fileHandleData_t fsh[MAX_FILE_HANDLES]; + +// never load anything from pk3 files that are not present at the server when pure +static int fs_numServerPaks; +static int fs_serverPaks[MAX_SEARCH_PATHS]; // checksums +static 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 +static int fs_numServerReferencedPaks; +static int fs_serverReferencedPaks[MAX_SEARCH_PATHS]; // checksums +static char *fs_serverReferencedPakNames[MAX_SEARCH_PATHS]; // pk3 names + +// last valid game folder used +char lastValidBase[MAX_OSPATH]; +char lastValidGame[MAX_OSPATH]; + +// productId: This file is copyright 2000 Raven Software, and may not be duplicated except during a licensed installation of the full commercial version of Star Wars: Jedi Outcast +static byte fs_scrambledProductId[165] = { + 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, 180, 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, 95, 164, 42, 36, 189, 94, 126 +}; + +#ifdef FS_MISSING +FILE* missingFiles = NULL; +#endif + +/* +============== +FS_Initialized +============== +*/ + +qboolean FS_Initialized() { + return (qboolean)(fs_searchpaths != NULL); +} + +/* +================= +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; +} + + +/* +================= +FS_LoadStack +return load stack +================= +*/ +int FS_LoadStack() +{ + return fs_loadStack; +} + +/* +================ +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 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; +} + +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_ReplaceSeparators + +Fix things up differently for win/unix/mac +==================== +*/ +static 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_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_CreatePath + +Creates any directories needed to store the given filename +============ +*/ +static 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 +================= +*/ +static 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 + +=========== +*/ +static 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; +} + +/* +=========== +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 +} + +static 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; +} + +static 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; +} + +static bool FS_FileCacheable(const char* const filename) +{ + return( strchr(filename, '/') && strnicmp(filename, "ext_data/", 9) ); +} + +/* +=========== +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, ".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, "jk2mpgamex86.dll", -13); + //]^%`cZT`Xk+)!W__ + FS_ShiftedStrStr(filename, "cgamex86.dll", -7); + //\`Zf^q1/']ee + FS_ShiftedStrStr(filename, "uix86.dll", -5); + //pds31)_gg + */ + + // jk2mpgame.qvm - 13 + // ]^%`cZT`X!di` + if (!(pak->referenced & FS_QAGAME_REF)) + { + if (FS_ShiftedStrStr(filename, "]^%`cZT`X!di`", 13) || + FS_ShiftedStrStr(filename, "]^%`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; +} + +#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_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 +#ifndef _XBOX // VVXXX + 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; + } +#endif + + // 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 +#ifndef _XBOX // VVXXX + 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 ); + } +#endif + return -1; + } + + if ( !buffer ) { +#ifndef _XBOX // VVXXX + 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 ); + } +#endif + 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 +#ifndef _XBOX // VVXXX + 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 ); + } +#endif + 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 ); +} + +/* +============ +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 ); +} + + + +/* +========================================================================== + +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 +} + +/* +================ +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 ) { + unzClose(p->pack->handle); + 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 +} + +#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_Startup +================ +*/ +static 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 ); + + // 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 +=================== +*/ +static 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" ); + } + + 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_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 ); + + // 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? +} + + +/* +================ +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 jk2mpconfig.cfg if "safe" is on the command line + if ( !Com_SafeMode() ) { +#ifdef DEDICATED + Cbuf_AddText ("exec jk2mpserver.cfg\n"); +#else + Cbuf_AddText ("exec jk2mpconfig.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/files.h b/codemp/qcommon/files.h new file mode 100644 index 0000000..390ec45 --- /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..ce4bd75 --- /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..51ee5fc --- /dev/null +++ b/codemp/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" + + + +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/codemp/qcommon/files_pc.cpp b/codemp/qcommon/files_pc.cpp new file mode 100644 index 0000000..be51e16 --- /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..f50e529 --- /dev/null +++ b/codemp/qcommon/fixedmap.h @@ -0,0 +1,148 @@ +#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) { + return false; + } + + //Check for reuse. + if(!FindUnsorted(key, storage)) { + 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..cc33834 --- /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/hstring.cpp b/codemp/qcommon/hstring.cpp new file mode 100644 index 0000000..22b9029 --- /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..3d1e9ae --- /dev/null +++ b/codemp/qcommon/huffman.cpp @@ -0,0 +1,417 @@ + +/* 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; +} + diff --git a/codemp/qcommon/md4.cpp b/codemp/qcommon/md4.cpp new file mode 100644 index 0000000..f7e7eee --- /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/msg.cpp b/codemp/qcommon/msg.cpp new file mode 100644 index 0000000..7dddcd4 --- /dev/null +++ b/codemp/qcommon/msg.cpp @@ -0,0 +1,3318 @@ +//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) + +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= +#ifdef _OPTIMIZED_VEHICLE_NETWORKING +//Instead of sending 2 full playerStates for the pilot and the vehicle, send a smaller, +//specialized pilot playerState and vehicle playerState. Also removes some vehicle +//fields from the normal playerState -mcg +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= + +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(holocronBits), 32 }, +{ 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 +//{ PSF(hyperSpaceAngles[0]), 0 },//only used by vehicle? +//{ PSF(hyperSpaceAngles[2]), 0 },//only used by vehicle? + +//rww - for use by mod authors only +#ifndef _XBOX +{ PSF(userInt1), 1 }, +{ PSF(userInt2), 1 }, +{ PSF(userInt3), 1 }, +{ PSF(userFloat1), 1 }, +{ PSF(userFloat2), 1 }, +{ PSF(userFloat3), 1 }, +{ PSF(userVec1[0]), 1 }, +{ PSF(userVec1[1]), 1 }, +{ PSF(userVec1[2]), 1 }, +{ PSF(userVec2[0]), 1 }, +{ PSF(userVec2[1]), 1 }, +{ PSF(userVec2[2]), 1 } +#endif +}; + +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(holocronBits), 32 }, +{ 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 +//===NEVER SEND THESE, ONLY USED BY VEHICLES============================================================== + +//{ PSF(vehOrientation[0]), 0 }, +//{ PSF(vehOrientation[1]), 0 }, +//{ PSF(vehOrientation[2]), 0 }, +//{ PSF(vehTurnaroundTime), 32 },//only used by vehicle? +//{ PSF(vehWeaponsLinked), 1 },//only used by vehicle? +//{ PSF(hyperSpaceTime), 32 },//only used by vehicle? +//{ 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(vehBoarding), 1 }, //only used by vehicle? not like the normal boarding value, this is a simple "1 or 0" value +//{ PSF(hyperSpaceAngles[1]), 0 },//only used by vehicle? +//{ PSF(hyperSpaceAngles[0]), 0 },//only used by vehicle? +//{ PSF(hyperSpaceAngles[2]), 0 },//only used by vehicle? + +//rww - for use by mod authors only +#ifndef _XBOX +{ PSF(userInt1), 1 }, +{ PSF(userInt2), 1 }, +{ PSF(userInt3), 1 }, +{ PSF(userFloat1), 1 }, +{ PSF(userFloat2), 1 }, +{ PSF(userFloat3), 1 }, +{ PSF(userVec1[0]), 1 }, +{ PSF(userVec1[1]), 1 }, +{ PSF(userVec1[2]), 1 }, +{ PSF(userVec2[0]), 1 }, +{ PSF(userVec2[1]), 1 }, +{ PSF(userVec2[2]), 1 } +#endif +}; + +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 }, + +//rww - for use by mod authors only +#ifndef _XBOX +{ PSF(userInt1), 1 }, +{ PSF(userInt2), 1 }, +{ PSF(userInt3), 1 }, +{ PSF(userFloat1), 1 }, +{ PSF(userFloat2), 1 }, +{ PSF(userFloat3), 1 }, +{ PSF(userVec1[0]), 1 }, +{ PSF(userVec1[1]), 1 }, +{ PSF(userVec1[2]), 1 }, +{ PSF(userVec2[0]), 1 }, +{ PSF(userVec2[1]), 1 }, +{ PSF(userVec2[2]), 1 } +#endif +}; + +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= +#else//_OPTIMIZED_VEHICLE_NETWORKING +//The unoptimized way, throw *all* the vehicle stuff into the playerState along with everything else... :( +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= + +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 }, +{ 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 }, +{ PSF(lastHitLoc[2]), 0 }, +{ PSF(hyperSpaceTime), 32 }, +{ 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 }, +{ 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 }, //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 }, +{ PSF(vehSurfaces), 16 }, //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(holocronBits), 32 }, +{ 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 +{ PSF(hyperSpaceAngles[0]), 0 }, +{ PSF(hyperSpaceAngles[2]), 0 }, + +//rww - for use by mod authors only +#ifndef _XBOX +{ PSF(userInt1), 1 }, +{ PSF(userInt2), 1 }, +{ PSF(userInt3), 1 }, +{ PSF(userFloat1), 1 }, +{ PSF(userFloat2), 1 }, +{ PSF(userFloat3), 1 }, +{ PSF(userVec1[0]), 1 }, +{ PSF(userVec1[1]), 1 }, +{ PSF(userVec1[2]), 1 }, +{ PSF(userVec2[0]), 1 }, +{ PSF(userVec2[1]), 1 }, +{ PSF(userVec2[2]), 1 } +#endif +}; + +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= +#endif//_OPTIMIZED_VEHICLE_NETWORKING +//=====_OPTIMIZED_VEHICLE_NETWORKING======================================================================= + +#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() { + 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..9d6933a --- /dev/null +++ b/codemp/qcommon/net_chan.cpp @@ -0,0 +1,657 @@ + +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + + +/* + +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 ) { + 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 +================= +*/ +void 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 ) { + 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 + NET_SendPacket( chan->sock, send.cursize, send.data, chan->remoteAddress ); + + if ( showpackets->integer ) { + Com_Printf ("%s send %4i : s=%i fragment=%i,%i\n" + , netsrcString[ chan->sock ] + , send.cursize + , chan->outgoingSequence - 1 + , chan->unsentFragmentStart, fragmentLength); + } + + 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; + } +} + + +/* +=============== +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]; + + 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 + Netchan_TransmitNextFragment( chan ); + + return; + } + + // 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 ) { + 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; + 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 + + //rww - not clearing this will allow us to piece together fragments belonging to other packets + //that happen to have the same sequence (or so it seems). I am just going to clear it and force + //the packet to be dropped. + + // hell yeah we have to dump the whole thing -gil + // but I am scared - mw + /* + chan->fragmentLength = 0; + chan->incomingSequence = sequence; + chan->fragmentSequence = 0; + */ + 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 = &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; + 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]; + + i = loop->send & (MAX_LOOPBACK-1); + loop->send++; + + Com_Memcpy (loop->msgs[i].data, data, length); + loop->msgs[i].datalen = length; +} + +//============================================================================= + + +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; + } + if ( to.type == NA_BOT ) { + return; + } + if ( to.type == NA_BAD ) { + 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 ); +} + +/* +=============== +NET_OutOfBandPrint + +Sends a data message in an out-of-band datagram (only used for "connect") +================ +*/ +void QDECL NET_OutOfBandData( netsrc_t sock, netadr_t adr, byte *format, int len ) { + byte string[MAX_MSGLEN*2]; + 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..bb71f02 --- /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..76f7306 --- /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..7e0cb1f --- /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..d53b858 --- /dev/null +++ b/codemp/qcommon/qcommon.h @@ -0,0 +1,1132 @@ +// 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 + +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 ); + +void 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_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); + + +#define MAX_MSGLEN 49152 // 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 ); + +void Netchan_Transmit( netchan_t *chan, int length, const byte *data ); +void 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 +#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 +#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_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 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 ); +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 +} 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 ); + +void Sys_SendPacket( int length, const void *data, netadr_t to ); +#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..4d962f8 --- /dev/null +++ b/codemp/qcommon/qfiles.h @@ -0,0 +1,606 @@ +#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? +//#define DRAWVERT_ST_SCALE 4096.0f +#define DRAWVERT_ST_SCALE 2048.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/sparc.h b/codemp/qcommon/sparc.h new file mode 100644 index 0000000..19ebeb6 --- /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..1b01eca --- /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..5e77588 --- /dev/null +++ b/codemp/qcommon/stringed_ingame.cpp @@ -0,0 +1,1264 @@ +// 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" +// +////////////////////////////////////////////////// + + +#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" + +/////////////////////////////////////////////// +// +// some STL stuff... +#pragma warning ( disable : 4786 ) // disable the usual stupid and pointless STL warning +#include +#include +#include +#include +#include +using namespace std; + +typedef vector vStrings_t; +typedef vector vInts_t; +// +/////////////////////////////////////////////// + +cvar_t *se_language = NULL; +cvar_t *se_debug = NULL; +cvar_t *sp_leet = NULL; // kept as 'sp_' for JK2 compat. + +#define __DEBUGOUT(_string) OutputDebugString(_string) +#define __ASSERT(_blah) assert(_blah) + +typedef struct SE_Entry_s +{ + string m_strString; + string m_strDebug; // english and/or "#same", used for debugging only. Also prefixed by "SE:" to show which strings go through StringEd (ie aren't hardwired) + int m_iFlags; + + SE_Entry_s() + { + m_iFlags = 0; + } + +} 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() + { + Clear( SE_FALSE ); + } + + ~CStringEdPackage() + { + Clear( SE_FALSE ); + } + + mapStringEntries_t m_StringEntries; // needs to be in public space now + SE_BOOL m_bLoadDebug; // "" + // + // flag stuff... + // + vector m_vstrFlagNames; + map m_mapFlagMasks; + + void Clear( SE_BOOL bChangingLanguages ); + void SetupNewFileParse( LPCSTR psFileName, SE_BOOL bLoadDebug ); + SE_BOOL ReadLine( LPCSTR &psParsePos, char *psDest ); + LPCSTR ParseLine( LPCSTR psLine ); + int GetFlagMask( LPCSTR psFlagName ); + 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 ); + void AddFlagReference( LPCSTR psLocalReference, LPCSTR psFlagName ); + 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(); + + if ( !bChangingLanguages ) + { + // if we're changing languages, then I'm going to leave these alone. This is to do with any (potentially) cached + // flag bitmasks on the game side. It shouldn't matter since all files are written out at once using the build + // command in StringEd. But if ever someone changed a file by hand, or added one, or whatever, and it had a + // different set of flags declared, or the strings were in a different order, then the flags might also change + // the order I see them in, and hence their indexes and masks. This should never happen unless people mess with + // the .STR files by hand and delete some, but this way makes sure it'll all work just in case... + // + // ie. flags stay defined once they're defined, and only the destructor (at app-end) kills them. + // + m_vstrFlagNames.clear(); + m_mapFlagMasks.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, SE_BOOL bLoadDebug ) +{ + 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; + m_bLoadDebug = bLoadDebug; +} + + +// 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(); +} + + +// returns flag bitmask (eg 00000010b), else 0 for not found +// +int CStringEdPackage::GetFlagMask( LPCSTR psFlagName ) +{ + map ::iterator itFlag = m_mapFlagMasks.find( psFlagName ); + if ( itFlag != m_mapFlagMasks.end() ) + { + int &iMask = (*itFlag).second; + return iMask; + } + + return 0; +} + + +void CStringEdPackage::AddFlagReference( LPCSTR psLocalReference, LPCSTR psFlagName ) +{ + // add the flag to the list of known ones... + // + int iMask = GetFlagMask( psFlagName ); + if (iMask == 0) + { + m_vstrFlagNames.push_back( psFlagName ); + iMask = 1 << (m_vstrFlagNames.size()-1); + m_mapFlagMasks[ psFlagName ] = iMask; + } + // + // then add the reference to this flag to the currently-parsed reference... + // + mapStringEntries_t::iterator itEntry = m_StringEntries.find( va("%s_%s",m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) ); + if (itEntry != m_StringEntries.end()) + { + SE_Entry_t &Entry = (*itEntry).second; + Entry.m_iFlags |= iMask; + } +} + +// 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_FLAGS, psLine)) + { + // FLAGS FLAG_CAPTION FLAG_TYPEMATIC + // + LPCSTR psReference = GetCurrentReference_ParseOnly(); + if (psReference[0]) + { + static const char sSeperators[] = " \t"; + char sFlags[1024]={0}; // 1024 chars should be enough to store 8 flag names + strncpy(sFlags, psLine, sizeof(sFlags)-1); + char *psToken = strtok( sFlags, sSeperators ); + while( psToken != NULL ) + { + // psToken = flag name (in caps) + // + Q_strupr(psToken); // jic + AddFlagReference( psReference, psToken ); + + // read next flag for this string... + // + psToken = strtok( NULL, sSeperators ); + } + } + else + { + psErrorMessage = "Error parsing file: Unexpected \"" sSE_KEYWORD_FLAGS "\"\n"; + } + } + 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; +} + +LPCSTR Leetify( LPCSTR psString ) +{ + static string str; + str = psString; + if (sp_leet->integer == 42) // very specific test, so you won't hit it accidentally + { + static const + char cReplace[]={ 'o','0','l','1','e','3','a','4','s','5','t','7','i','!','h','#', + 'O','0','L','1','E','3','A','4','S','5','T','7','I','!','H','#' // laziness because of strchr() + }; + + char *p; + for (int i=0; i -> 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 bLoadDebug = SE_TRUE, 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, bLoadDebug, 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, bLoadDebug, 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 ) +{ + char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long + assert(strlen(psPackageAndStringReference) < sizeof(sReference) ); + Q_strncpyz(sReference, psPackageAndStringReference, sizeof(sReference) ); + Q_strupr(sReference); + + mapStringEntries_t::iterator itEntry = TheStringPackage.m_StringEntries.find( sReference ); + if (itEntry != TheStringPackage.m_StringEntries.end()) + { + SE_Entry_t &Entry = (*itEntry).second; + + if ( se_debug->integer && TheStringPackage.m_bLoadDebug ) + { + return Entry.m_strDebug.c_str(); + } + else + { + return Entry.m_strString.c_str(); + } + } + + // should never get here, but fall back anyway... (except we DO use this to see if there's a debug-friendly key bind, which may not exist) + // +// __ASSERT(0); + return ""; // you may want to replace this with something based on _DEBUG or not? +} + + +// convenience-function for the main GetFlags call... +// +int SE_GetFlags ( 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_GetFlags( sReference ); +} + +int SE_GetFlags ( LPCSTR psPackageAndStringReference ) +{ + mapStringEntries_t::iterator itEntry = TheStringPackage.m_StringEntries.find( psPackageAndStringReference ); + if (itEntry != TheStringPackage.m_StringEntries.end()) + { + SE_Entry_t &Entry = (*itEntry).second; + + return Entry.m_iFlags; + } + + // should never get here, but fall back anyway... + // + __ASSERT(0); + + return 0; +} + + +int SE_GetNumFlags( void ) +{ + return TheStringPackage.m_vstrFlagNames.size(); +} + +LPCSTR SE_GetFlagName( int iFlagIndex ) +{ + if ( iFlagIndex < TheStringPackage.m_vstrFlagNames.size()) + { + return TheStringPackage.m_vstrFlagNames[ iFlagIndex ].c_str(); + } + + __ASSERT(0); + return ""; +} + +// returns flag bitmask (eg 00000010b), else 0 for not found +// +int SE_GetFlagMask( LPCSTR psFlagName ) +{ + return TheStringPackage.GetFlagMask( psFlagName ); +} + +// I could cache the result of this since it won't change during app lifetime unless someone does a build-publish +// while you're still ingame. Cacheing would make sense since it can take a while to scan, but I'll leave it and +// let whoever calls it cache the results instead. I'll make it known that it's a slow process to call this, but +// whenever anyone calls someone else's code they should assign it to an int anyway, since you've no idea what's +// going on. Basically, don't use this in a FOR loop as the end-condition. Duh. +// +// Groan, except for Bob. I mentioned that this was slow and only call it once, but he's calling it from +// every level-load... Ok, cacheing it is... +// +vector gvLanguagesAvailable; +int SE_GetNumLanguages(void) +{ + if ( gvLanguagesAvailable.empty() ) + { + 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 + ); + + set strUniqueStrings; // laziness + LPCSTR p; + while ((p=SE_GetFoundFile (strResults)) != NULL) + { + LPCSTR psLanguage = TheStringPackage.ExtractLanguageFromPath( p ); + + // __DEBUGOUT( p ); + // __DEBUGOUT( "\n" ); + // __DEBUGOUT( psLanguage ); + // __DEBUGOUT( "\n" ); + + if (!strUniqueStrings.count( psLanguage )) + { + strUniqueStrings.insert( psLanguage ); + + // if english is available, it should always be first... ( I suppose ) + // + if (!stricmp(psLanguage,"english")) + { + gvLanguagesAvailable.insert( gvLanguagesAvailable.begin(), psLanguage ); + } + else + { + gvLanguagesAvailable.push_back( psLanguage ); + } + } + } + } + + return gvLanguagesAvailable.size(); +} + +// SE_GetNumLanguages() must have been called before this... +// +LPCSTR SE_GetLanguageName( int iLangIndex ) +{ + if ( iLangIndex < gvLanguagesAvailable.size() ) + { + return gvLanguagesAvailable[ iLangIndex ].c_str(); + } + + __ASSERT(0); + return ""; +} + +// SE_GetNumLanguages() must have been called before this... +// +LPCSTR SE_GetLanguageDir( int iLangIndex ) +{ + if ( iLangIndex < gvLanguagesAvailable.size() ) + { + return va("%s/%s", sSE_STRINGS_DIR, gvLanguagesAvailable[ iLangIndex ].c_str() ); + } + + __ASSERT(0); + return ""; +} + +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) +{ +#ifdef _XBOX // VVFIXME? +// extern void Z_SetNewDeleteTemporary(bool); +// Z_SetNewDeleteTemporary(true); +#endif + + TheStringPackage.Clear( SE_FALSE ); + +#ifdef _DEBUG +// int iNumLanguages = SE_GetNumLanguages(); +#endif + + se_language = Cvar_Get("se_language", "english", CVAR_ARCHIVE | CVAR_NORESTART); + se_debug = Cvar_Get("se_debug", "0", 0); + sp_leet = Cvar_Get("sp_leet", "0", CVAR_ROM ); + + // if doing a buildscript, load all languages... + // + extern cvar_t *com_buildScript; + if (com_buildScript->integer == 2) + { + int iLanguages = SE_GetNumLanguages(); + for (int iLang = 0; iLang < iLanguages; iLang++) + { + LPCSTR psLanguage = SE_GetLanguageName( iLang ); // eg "german" + Com_Printf( "com_buildScript(2): Loading language \"%s\"...\n", psLanguage ); + SE_LoadLanguage( psLanguage ); + } + } + + LPCSTR psErrorMessage = SE_LoadLanguage( se_language->string ); + if (psErrorMessage) + { + Com_Error( ERR_DROP, "SE_Init() Unable to load language: \"%s\"!\nError: \"%s\"\n", se_language->string,psErrorMessage ); + } + +#ifdef _XBOX +// Z_SetNewDeleteTemporary(false); +#endif +} + +void SE_ShutDown(void) +{ + TheStringPackage.Clear( SE_FALSE ); +} + + +// 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, SE_BOOL bLoadDebug /* = SE_TRUE */ ) +{ + 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, bLoadDebug ); + } + } + } + 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, SE_TRUE ); + if ( psErrorMessage ) + { + Com_Error( ERR_DROP, psErrorMessage ); + } + 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..107951c --- /dev/null +++ b/codemp/qcommon/stringed_ingame.h @@ -0,0 +1,110 @@ +// 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" +#define sSE_DEBUGSTR_PREFIX "[" // any string you want prefixing onto the debug versions of strings (to spot hardwired english etc) +#define sSE_DEBUGSTR_SUFFIX "]" // "" + +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_FLAGS "FLAGS" +#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_ShutDown ( void ); +void SE_CheckForLanguageUpdates(void); +int SE_GetNumLanguages ( void ); +LPCSTR SE_GetLanguageName ( int iLangIndex ); // eg "german" +LPCSTR SE_GetLanguageDir ( int iLangIndex ); // eg "strings/german" +LPCSTR SE_LoadLanguage ( LPCSTR psLanguage, SE_BOOL bLoadDebug = SE_TRUE ); +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); +// +// ditto... +// +int SE_GetFlags ( LPCSTR psPackageReference, LPCSTR psStringReference ); +int SE_GetFlags ( LPCSTR psPackageAndStringReference ); +// +// general flag functions... (SEP_GetFlagMask() return should be used with SEP_GetFlags() return) +// +int SE_GetNumFlags ( void ); +LPCSTR SE_GetFlagName ( int iFlagIndex ); +int SE_GetFlagMask ( LPCSTR psFlagName ); + + +// note that so far the only place in the game that needs to know these is the font system so it can know how to +// interpret char codes, for this reason I'm only exposing these simple bool queries... +// +inline SE_BOOL Language_IsRussian(void) +{ + return (se_language && !Q_stricmp(se_language->string, "russian")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsPolish(void) +{ + return (se_language && !Q_stricmp(se_language->string, "polish")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsKorean(void) +{ + return (se_language && !Q_stricmp(se_language->string, "korean")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsTaiwanese(void) +{ + return (se_language && !Q_stricmp(se_language->string, "taiwanese")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsJapanese(void) +{ + return (se_language && !Q_stricmp(se_language->string, "japanese")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsChinese(void) +{ + return (se_language && !Q_stricmp(se_language->string, "chinese")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsThai(void) +{ + return (se_language && !Q_stricmp(se_language->string, "thai")) ? SE_TRUE : SE_FALSE; +} + + +#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..b23b9fa --- /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/tags.h b/codemp/qcommon/tags.h new file mode 100644 index 0000000..c8a4635 --- /dev/null +++ b/codemp/qcommon/tags.h @@ -0,0 +1,75 @@ +// 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 +#ifndef _XBOX + TAGDEF(BOTGAME), + TAGDEF(DOWNLOAD), // used by the downloading system + TAGDEF(GENERAL), + TAGDEF(CLIPBOARD), + TAGDEF(SND_MP3STREAMHDR), // specific MP3 struct for decoding (about 18..22K each?), not the actual MP3 binary + TAGDEF(SND_DYNAMICMUSIC), // in-mem MP3 files + TAGDEF(BSP_DISKIMAGE), // temp during loading, to save both server and renderer fread()ing the same file. Only used if not low physical memory (currently 96MB) + TAGDEF(VM), // stuff for VM, may be zapped later? + TAGDEF(SPECIAL_MEM_TEST), // special usage for testing z_malloc recover only +#endif + 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(GHOUL2_GORE), // Ghoul2 gore 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(INFLATE), // Temp memory used by zlib32 + TAGDEF(DEFLATE), // Temp memory used by zlib32// TAGDEF(SOUNDPOOL), // pool of mem for the sound system + TAGDEF(BSP), // guess. + TAGDEF(GRIDMESH), // some specific temp workspace that only seems to be in the MP codebase + + //rwwRMG - following: + TAGDEF(POINTCACHE), // weather system + TAGDEF(TERRAIN), // RMG terrain management + TAGDEF(R_TERRAIN), // terrain renderer + TAGDEF(RESAMPLE), // terrain heightmap resampling (I think) + TAGDEF(CM_TERRAIN), // common terrain data management + TAGDEF(CM_TERRAIN_TEMP), // temporary terrain allocations + TAGDEF(TEMP_IMAGE), // temporary allocations for image manipulation + + TAGDEF(VM_ALLOCATED), // allocated by game or cgame via memory shifting + + TAGDEF(TEMP_HUNKALLOC), +#ifdef _XBOX + 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 +#endif + TAGDEF(COUNT) + + +//////////////// eof ////////////// + diff --git a/codemp/qcommon/timing.h b/codemp/qcommon/timing.h new file mode 100644 index 0000000..c825fad --- /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..02e8edd --- /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..e7fc924 --- /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..29a3277 --- /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..4ad0129 --- /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..cfefb16 --- /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..dc1e545 --- /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..7fff987 --- /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..ca88d40 --- /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/vssver.scc b/codemp/qcommon/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..3f48578c3e0ea9a4b93cd626efe39842256c73cc GIT binary patch literal 1184 zcmW;LeNfYN90%~9j(E}o;$UgOdVnm7B$RQPY(6c~S#c2i{rE{xSe%_WX961&$6yE| zDCl^YO2*|-AfLFnh;a>0q14mtBx{zy3~~i01c<5Wu=nq?Kfe3EexL8-=llJ}2F_u$ z-v?$@x;hn2gApG{GVixU6*XwL&SB`M#(&7sRrW!3ddggU(ZIpS-dp?rm9P{acsu-~ zC@Rz$Ax-gxKZ9qk+z7c_e}VIccfgAJ)|JJ!i=5Cu2VT^8^}%1d+vHr>+xPR(dy)wb zRVELGe7@9}zULk%g0*n%>^!%-wUP6Jb%LcQK8=Y_rt$OPrmlpnv+rhd^AOj=svo|* zmDlD;6Mdc)<}<+KBeG;!m5CMh zR|qFfI@2P%lUPsK2)9|M8V*K$$a=s<@VT+YdjH&Qtgydgct))14SP;UHo;TAN3%;y z3t3c|5)?0MGPkOicu;&d?9`Vf9M<_$ycFK#c}I3u5Cb~d11Z# z;CxZBzHajnc|Rk-%o?7_E^M--Q4PMm04j#U)`}!N}0^Sep>){pQEL*9U(tH*;=c`Tf z99KLqc0!Vp4E9~y*iTSH^8|kOMb5@cclvFT?nuK*Aln%ba|>U|1r3+EU@2rq>ApB z6;_Gg*llwiq5IziOD0?D6Zcxl$Ki8-m&CPiTg3|fuLa{x_KTBr-O-faez>L~Y`O80 zlk)d7{CJCP@xsw&+Rp_1D1C?-<<*q0f8g3%J+D9cNK5S}Vda=+eDkP*Tp?zdPg;HE KhFI3oe*XvCK1 +#endif + +#ifdef _WINDOWS +#include +#endif + +#ifdef _XBOX +#include +#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 + +// Allocate all available memory minus this amount +#ifdef _GAMECUBE +#define ZONE_HEAP_FREE_DEBUG (64*1024*4) +#define ZONE_HEAP_FREE_RELEASE (0) + +#ifdef _DEBUG +# define ZONE_HEAP_FREE ZONE_HEAP_FREE_DEBUG +#else +# define ZONE_HEAP_FREE ZONE_HEAP_FREE_RELEASE +#endif +#else +//Game needs about 8 MB for framebuffers audio, bink, etc., plus 24 MB +//for textures. +//# define ZONE_HEAP_FREE (1024*1024*8 + 24*1024*1024) +# define ZONE_HEAP_FREE (1024*1024*8 + 24*1024*1024 + 4*1024*1024) +#endif + +// 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 bool s_IsNewDeleteTemp = false; + +#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); + + +#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; + s_IsNewDeleteTemp = false; + + // Alloc the pool +#if defined(_XBOX) + 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); + 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); +#elif defined(_WINDOWS) + SIZE_T size = 50*1024*1024; + s_PoolBase = GlobalAlloc(0, size); +#endif + + // 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); + +#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 || +#ifndef _JK2MP + eTag == TAG_TEMP_SAVEGAME_WORKSPACE || + eTag == TAG_STRING || + eTag == TAG_GP2 || +#endif + eTag == TAG_SND_RAWDATA || + eTag == TAG_ICARUS || +#ifdef _JK2MP + eTag == TAG_TEXTPOOL || + eTag == TAG_TEMP_HUNKALLOC || +#endif + eTag == TAG_LISTFILES; +} + +// Determine if a tag needs TagFree() support. +static bool Z_IsTagLinked(memtag_t eTag) +{ + return + eTag == TAG_BSP || +#ifndef _JK2MP + eTag == TAG_HUNKALLOC || + eTag == TAG_HUNKMISCMODELS || + eTag == TAG_G_ALLOC || +#endif +#ifdef _JK2MP + eTag == TAG_CG_UI_ALLOC || + eTag == TAG_BG_ALLOC || + eTag == TAG_HUNK_MARK1 || + eTag == TAG_HUNK_MARK2 || + eTag == TAG_TEMP_HUNKALLOC || +#endif + 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 && s_IsNewDeleteTemp) + { + eTag = TAG_TEMP_WORKSPACE; + } + + // 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; + } + */ + +#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 + + Com_Printf("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 + Com_Printf("%x - %d\n", ((void*)((char*)header + + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader))), + Z_Size(((void*)((char*)header + + sizeof(ZoneHeader) + sizeof(ZoneDebugHeader))))); +#else + Com_Printf("%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) +{ + // Catch nested uses that break when unwinding the stack + assert(bTemp != s_IsNewDeleteTemp); + s_IsNewDeleteTemp = bTemp; +} + +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_GetTerrainMemory(void) +{ + return s_Stats.m_SizesPerTag[TAG_CM_TERRAIN] + + s_Stats.m_SizesPerTag[TAG_CM_TERRAIN_TEMP] + +#ifdef _JK2MP + s_Stats.m_SizesPerTag[TAG_TERRAIN] + +#endif + s_Stats.m_SizesPerTag[TAG_R_TERRAIN]; +} + +int Z_GetMiscMemory(void) +{ + return s_Stats.m_SizeAlloc - + (Z_GetLevelMemory() + +#ifdef _JK2MP + Z_GetHunkMemory() + +#endif + Z_GetTerrainMemory() + + 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); + + //This report is conservative. Divides by 1000 instead of 1024 and + //then rounds up. + Sys_Log("memory-map.txt", va("**Z_CompactStats Start**\n")); + Sys_Log("memory-map.txt", va("map: %s\n", Cvar_VariableString( "mapname" )) ); + + Sys_Log("memory-map.txt", va("OV: %d, LVL: %d, GLM: %d, GLA: %d, MD3: %d\n", + (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)); + + Sys_Log("memory-map.txt", va("TER: %d, SND: %d, TEX: %d, FMV: %d, MSC: %d\n", + (Z_GetTerrainMemory() / 1000) + 1, + (s_Stats.m_SizesPerTag[TAG_SND_RAWDATA] / 1000) + 1, + (texMemSize / 1000) + 1, + (s_Stats.m_SizesPerTag[TAG_BINK] / 1000) + 1, + (Z_GetMiscMemory() / 1000) + 1)); + +#ifdef _JK2MP + Sys_Log("memory-map.txt", va("HUNK: %d, THUNK: %d\n", + ((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)); +#endif + + Sys_Log("memory-map.txt", va("Free Zone: %d\n", s_Stats.m_SizeFree)); + +} + + +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) +{ + 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); +} + +*/ diff --git a/codemp/qcommon/z_memman_pc.cpp b/codemp/qcommon/z_memman_pc.cpp new file mode 100644 index 0000000..d15e5da --- /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/renderer/glext.h b/codemp/renderer/glext.h new file mode 100644 index 0000000..63dbe82 --- /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..0220e94 --- /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..faa1efb --- /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/qgl.h b/codemp/renderer/qgl.h new file mode 100644 index 0000000..8214b73 --- /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..0e35b09 --- /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_WorldEffects.cpp b/codemp/renderer/tr_WorldEffects.cpp new file mode 100644 index 0000000..637a42c --- /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/renderer/tr_animation.cpp b/codemp/renderer/tr_animation.cpp new file mode 100644 index 0000000..1e65c46 --- /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..d5eb347 --- /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..cfa7010 --- /dev/null +++ b/codemp/renderer/tr_backend.cpp @@ -0,0 +1,2328 @@ +//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 VV_LIGHTING +#include "tr_lightmanager.h" +#endif + +#ifdef _XBOX +#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; + + //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; + 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; + } + } + /* + else if (shader == tr.distortionShader && + g_numPostRenders < MAX_POST_RENDERS) + { //not an ent, just a surface that needs this effect + pRender = &g_postRenders[g_numPostRenders]; + + g_numPostRenders++; + + depthRange = 0; + 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; + + pRender->eValid = qfalse; + + //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 ) { +#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 = 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; +#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 ); + } + + 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 ); + + /* + if (!pRender->eValid && pRender->entNum == TR_WORLDENT) + { //world/other surface + 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; + +#ifdef VV_LIGHTING + VVLightMan.R_TransformDlights( &backEnd.ori ); +#else + R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori ); +#endif + } + else + */ + { //ent + 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 ) { +#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 (!pRender->eValid) + { //special full-screen "distortion" (or refraction or whatever the heck you want to call it) + if (!tr_stencilled) + { //only need to do this once every frame (that a surface using it is around) + 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. +#if 0 + 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); +#endif + + 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; + } + + qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, cX, cY, radX, radY, 0); + } + lastPostEnt = ENTITYNUM_NONE; + } + */ + 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; + bool r; + //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 0 //yeah.. this kinda worked but it was stupid + if (pRender->eValid) + { + r = R_WorldCoordToScreenCoord( backEnd.currentEntity->e.origin, &x, &y ); + rad = backEnd.currentEntity->e.radius; + } + else + { + vec3_t v; + //probably a little bit expensive.. but we're doing this for looks, not speed! + if (!R_AverageTessXYZ(v)) + { //failed, just use first vert I guess + VectorCopy(tess.xyz[0], v); + } + r = R_WorldCoordToScreenCoord( v, &x, &y ); + rad = 256; + } +#else + r = R_WorldCoordToScreenCoord( backEnd.currentEntity->e.origin, &x, &y ); + rad = backEnd.currentEntity->e.radius; +#endif + + if (r) + { + 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 (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 + +================ +*/ +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 + 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()*com_timescale->value; + backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f; +} + + +/* +============= +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) +{ + 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 ); + + // clear screen for debugging + 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 ); + qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + } + else if ( r_clear->integer ) { + 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 ); + } + + 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..128aa3f --- /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..b51af9d --- /dev/null +++ b/codemp/renderer/tr_bsp_xbox.cpp @@ -0,0 +1,1765 @@ +// 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; + +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; +} + + +/* +================= +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 int *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 + 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; + int 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; + int 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]; + } + + 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); + + 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; + + int 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; + + 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); + + 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; + int 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" ); + } + + 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); + + Lump flares; + flares.load(stripName, "flares"); + R_LoadFlares(flares.data, flares.len); + + 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; + + // 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 ) +{ + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!! + + RE_LoadWorldMap_Actual( name, s_worldData, 0 ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!! +} diff --git a/codemp/renderer/tr_cmds.cpp b/codemp/renderer/tr_cmds.cpp new file mode 100644 index 0000000..16098d3 --- /dev/null +++ b/codemp/renderer/tr_cmds.cpp @@ -0,0 +1,476 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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 + 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; +} + + +/* +============= +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..503edad --- /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..f89726a --- /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..38eb89d --- /dev/null +++ b/codemp/renderer/tr_flares.cpp @@ -0,0 +1,428 @@ +// 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; + + 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..72f2952 --- /dev/null +++ b/codemp/renderer/tr_font.cpp @@ -0,0 +1,1714 @@ +// 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!!!!! +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +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() +{ + 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... + // + 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 + { + 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; + } + } + +// // test code only +// if (GetLanguageEnum() == eTaiwanese) +// { +// psText = "Wp:¶}·F§a ¿p·G´µ¡A§Æ±æ§A¹³¥L­Ì»¡ªº¤@¼Ë¦æ¡C"; +// } +// else +// if (GetLanguageEnum() == eChinese) +// { +// //psText = "Ó¶±øÕ½³¡II Ô¼º²?ĪÁÖ˹ ÈÎÎñʧ°Ü ÄãÒªÌ×Óû­ÃæÉ趨µÄ±ä¸üÂ𣿠ԤÉè,S3 ѹËõ,DXT1 ѹËõ,DXT5 ѹËõ,16 Bit,32 Bit"; +// psText = "Ó¶±øÕ½³¡II"; +// } +// else +// if (GetLanguageEnum() == eThai) +// { +// //psText = "Áҵðҹ¼ÅÔµÀѳ±ìÍصÊÒË¡ÃÃÁÃËÑÊÊÓËÃѺÍÑ¡¢ÃÐä·Â·Õèãªé¡Ñº¤ÍÁ¾ÔÇàµÍÃì"; +// psText = "Áҵðҹ¼ÅÔµ"; +// psText = "ÃËÑÊÊÓËÃѺ"; +// psText = "ÃËÑÊÊÓËÃѺ ÍÒ_¡Ô¹_¤ÍÃì·_1415"; +// } +// else +// if (GetLanguageEnum() == eKorean) +// { +// psText = "Wp:¼îŸÀÓÀÌ´Ù ¸Ö¸°. ±×µéÀÌ ¸»ÇÑ´ë·Î ³×°¡ ÀßÇÒÁö ±â´ëÇÏ°Ú´Ù."; +// } +// else +// if (GetLanguageEnum() == eJapanese) +// { +// static char sBlah[200]; +// sprintf(sBlah,va("%c%c%c%c%c%c%c%c",0x82,0xA9,0x82,0xC8,0x8A,0xBF,0x8E,0x9A)); +// psText = &sBlah[0]; +// } +// else +// if (GetLanguageEnum() == eRussian) +// { +//// //psText = "Íà âåðøèíå õîëìà ñòîèò ñòàðûé äîì ñ ïðèâèäåíèÿìè è áàøíÿ ñ âîëøåáíûìè ÷àñàìè." +// psText = "Íà âåðøèíå õîëìà ñòîèò"; +// } +// else +// if (GetLanguageEnum() == ePolish) +// { +// psText = "za³o¿ony w 1364 roku, jest najstarsz¹ polsk¹ uczelni¹ i nale¿y..."; +// psText = "za³o¿ony nale¿y"; +// } + + + 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 '_': // 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..f46e611 --- /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..2431451 --- /dev/null +++ b/codemp/renderer/tr_ghoul2.cpp @@ -0,0 +1,5509 @@ +// 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" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.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]; +#if 0 //this is just too slow. I need a better way. + static float smoothFactor; + + smoothFactor = mSmoothFactor; + + //Special rag smoothing -rww + if (smoothFactor < 0) + { //I need a faster way to do this but I do not want to store more in the bonecache + static int blistIndex; + assert(mod); + assert(rootBoneList); + blistIndex = G2_Find_Bone_ByNum(mod, *rootBoneList, index); + + assert(blistIndex != -1); + + boneInfo_t &bone = (*rootBoneList)[blistIndex]; + + if (bone.flags & BONE_ANGLES_RAGDOLL) + { + if ((bone.RagFlags & (0x00008)) || //pelvis + (bone.RagFlags & (0x00004))) //model_root + { //pelvis and root do not smooth much + smoothFactor = 0.2f; + } + else if (bone.solidCount > 4) + { //if stuck in solid a lot then snap out quickly + smoothFactor = 0.1f; + } + else + { //otherwise smooth a bunch + smoothFactor = 0.8f; + } + } + else + { //not a rag bone + smoothFactor = 0.3f; + } + } +#endif + + 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... ;-) +} + + +/*static inline*/ void UnCompressBone(float mat[3][4], int iBoneIndex, const mdxaHeader_t *pMDXAHeader, int iFrame) +{ + 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[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[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[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[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[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[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 +#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 ); + + // 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_SIZE); + Q_CastShort2FloatScale(&out->texCoords[baseVert][0][1], &texCoord->texCoords[1], 1.f / GLM_COMP_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_SIZE); + Q_CastShort2FloatScale(out + 4, &texCoord->texCoords[1], 1.f / GLM_COMP_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; + +#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;j> 16)) + { + DWORD a = (tess.svars.colors[j+baseVertex] & 0xff000000) >> 24; + tess.svars.colors[j+baseVertex] = D3DCOLOR_RGBA(lFade, lFade, lFade, lFade); + } + else + { + DWORD rgb = tess.svars.colors[j+baseVertex] & 0x00ffffff; + tess.svars.colors[j+baseVertex] = rgb | ((lFade & 0xff) << 24); + } +#else + if (lFade < tess.svars.colors[j+baseVertex][0]) + { //don't set it unless the fade is less than the current r value (to avoid brightening suddenly before we start fading) + tess.svars.colors[j+baseVertex][0] = tess.svars.colors[j+baseVertex][1] = tess.svars.colors[j+baseVertex][2] = lFade; + } + + //Set the alpha as well I suppose, no matter what + tess.svars.colors[j+baseVertex][3] = lFade; +#endif + } + } + } + + glIndex_t *indexPtr = &tess.indexes[baseIndex]; + triangles = data; + for (j = indexes ; j ; j--) + { + *indexPtr++ = baseVertex + (*triangles++); + } + tess.numIndexes += indexes; + tess.numVertexes += numVerts; + return; + } +#endif + + // grab the pointer to the surface info within the loaded mesh file + mdxmSurface_t *surface = surf->surfaceData; + + CBoneCache *bones = surf->boneCache; + +#ifndef _G2_GORE //we use this later, for gore + delete surf; +#endif + +#ifdef VV_LIGHTING + // Set any dynamic lighting needed + if(backEnd.currentEntity->dlightBits) + tess.dlightBits = backEnd.currentEntity->dlightBits; +#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 + CRenderableSurface *storeSurf = surf; + + 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]hasGlow || g_bRenderGlowingObjects || !g_bDynamicGlowSupported || !r_DynamicGlow->integer ) + { + delete storeSurf; + } +#endif +#endif + + tess.numVertexes += surface->numVerts; + +#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; + 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... + // + bAlreadyCached = qtrue; + assert( mdxm == buffer ); +// memcpy( mdxm, buffer, size ); // and don't do this now, since it's the same thing + + 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; + } + + 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 + + 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); + + 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... + // + bAlreadyCached = qtrue; + assert( mdxa == buffer ); +// memcpy( mdxa, buffer, size ); // and don't do this now, since it's the same thing +#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..88be3b3 --- /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 int *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..849942b --- /dev/null +++ b/codemp/renderer/tr_image_xbox.cpp @@ -0,0 +1,2579 @@ +// 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" + + +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"}; + + 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->mipcount] ); + + 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( "???? " ); + } + + 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; + } + + 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 +================ +*/ +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) +{ + 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); + } + + AllocatedImages->clear(); + + giTextureBindNum = 1024; +} + + +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... + //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 + Com_Printf( "WARNING: reused image %s with mixed mipmap parm\n", pName ); + } + 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)); + + 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 ) { + int bytedepth; + 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 ) ); + } + + *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; + } + + COM_StripExtension(name,name); + COM_DefaultExtension(name, sizeof(name), ".png"); + + //No .tga existed, try .png + LoadPNG32( name, pic, width, height, &bytedepth ); + if (*pic) + { + return; + } + + // No .png either, fall back to .dds + COM_StripExtension(name,name); + COM_DefaultExtension(name, sizeof(name), ".dds"); + LoadDDS( name, pic, width, height, mipcount, format ); + 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) + { //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(); +} + +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 ) +{ + int x,y; + byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; + int xs, ys; + int b; + + // The old code claims to have made a centered inverse-square falloff blob for dynamic lighting + // and it looked nasty, so, just doing something simpler that seems to have a much softer result + for ( x = 0; x < DLIGHT_SIZE; x++ ) + { + for ( y = 0; y < DLIGHT_SIZE; y++ ) + { + xs = (DLIGHT_SIZE * 0.5f - x); + ys = (DLIGHT_SIZE * 0.5f - y); + + b = 255 - sqrt( (float)(xs * xs + ys * ys) ) * 9.0f; // try and generate numbers in the range of 255-0 + + // should be close, but clamp anyway + if ( b > 255 ) + { + b = 255; + } + else if ( b < 0 ) + { + 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, 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 int *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; + } + // allocate a new skin + tr.numSkins++; + skin = (skin_t*) Hunk_Alloc( sizeof( skin_t ), h_low ); + 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; +*/ + } + + 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 ) { + 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..0559da6 --- /dev/null +++ b/codemp/renderer/tr_init.cpp @@ -0,0 +1,1522 @@ +//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; +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); + + 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 + 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 + 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; +#ifndef VV_LIGHTING + re.AddLightToScene = RE_AddLightToScene; + re.AddAdditiveLightToScene = RE_AddAdditiveLightToScene; +#endif + 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..bfdd78e --- /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..487283c --- /dev/null +++ b/codemp/renderer/tr_light.cpp @@ -0,0 +1,483 @@ +//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 +============= +*/ +#ifndef VV_LIGHTING +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; + } + } +} +#endif + + +/* +============================================================================= + +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 + +================= +*/ +#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) + { + 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 ) { +#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 ( 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] ); +#endif // VV_LIGHTING +} + +/* +================= +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_local.h b/codemp/renderer/tr_local.h new file mode 100644 index 0000000..d23dc75 --- /dev/null +++ b/codemp/renderer/tr_local.h @@ -0,0 +1,2365 @@ +#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; + 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; + + +#ifdef _XBOX +typedef struct image_s { + int imgCode; + USHORT width, height; // source image + + GLuint texnum; // gl texture binding + int internalFormat; + int wrapClampMode; // GL_CLAMP or GL_REPEAT + + bool isLightmap; + bool isSystem; + int mipcount; + + bool allowPicmip; + short iLastLevelUsedOn; +} image_t; + +#else // _XBOX + +typedef struct image_s { + char imgName[MAX_QPATH]; // game path, including extension + USHORT width, height; // after power of two and picmip but not including clamp to MAX_TEXTURE_SIZE + GLuint texnum; // gl texture binding + + int frameUsed; // for texture usage in frame statistics + + int internalFormat; + int wrapClampMode; // GL_CLAMP or GL_REPEAT + + bool mipmap; + bool allowPicmip; + + short iLastLevelUsedOn; + +} image_t; +#endif // _XBOX + +//=============================================================================== + +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; + + texCoordGen_t tcGen; + vec3_t *tcGenVectors; + + texModInfo_t *texMods; + short numTexMods; + short numImageAnimations; + float imageAnimationSpeed; + + bool isLightmap; + bool oneShotAnimMap; + bool vertexLightmap; + bool isVideoMap; + + int videoMapHandle; +} textureBundle_t; + + +#define NUM_TEXTURE_BUNDLES 2 + +typedef struct { + bool active; + bool isDetail; +#ifdef _XBOX + byte isEnvironment; +#endif +#ifdef VV_LIGHTING + byte isSpecular; + byte isBumpMap; +#endif + byte index; // index of stage + byte lightmapStyle; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + colorGen_t rgbGen; + + waveForm_t alphaWave; + alphaGen_t alphaGen; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned int stateBits; // GLS_xxxx mask + + acff_t adjustColorsForFog; + + EGLFogOverride mGLFogColorOverride; + + surfaceSprite_t *ss; + + // Whether this object emits a glow or not. + bool glow; +} 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 + int lightmapIndex[MAXLIGHTMAPS]; // for a shader to match, both name and lightmapIndex must match + byte styles[MAXLIGHTMAPS]; + + int index; // this shader == tr.shaders[index] + int 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; + + bool 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 + bool explicitlyDefined; // found in a .shader file + bool entityMergable; // merge across entites optimizable (smoke, blood) + + bool isBumpMap; + +#ifdef _XBOX + bool needsNormal; + bool needsTangent; +#endif + + 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 + bool polygonOffset; // set for decals and other items that must be offset + bool 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) + + 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; + +#ifndef VV_LIGHTING + int num_dlights; + struct dlight_s *dlights; +#endif + + 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]; +} 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 +#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 2 +#else +#define NUM_SCRATCH_IMAGES 16 +#endif + +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; +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 ); + +#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_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 + +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); +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, 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 int lightmapsNone[MAXLIGHTMAPS]; +extern const int lightmaps2d[MAXLIGHTMAPS]; +extern const int lightmapsVertex[MAXLIGHTMAPS]; +extern const int lightmapsFullBright[MAXLIGHTMAPS]; +extern const byte stylesDefault[MAXLIGHTMAPS]; + +qhandle_t RE_RegisterShaderLightMap( const char *name, const int *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, int *lightmapIndex, byte *styles, image_t *image, qboolean mipRawImage); + +shader_t *R_FindShader( const char *name, const int *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 + +#ifdef _XBOX +typedef struct +{ + char levelName[_MAX_PATH]; + vec3_t sundir; + bool hdrEnable; + float hdrBloom; +} 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]; //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 ); +#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 ); +#endif // _XBOX +void RB_CalcDiffuseColor( unsigned char *colors ); +void RB_CalcDiffuseEntityColor( unsigned char *colors ); +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; +} 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]; +#ifndef VV_LIGHTING + dlight_t dlights[MAX_DLIGHTS]; +#endif + 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); + +/* +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 + +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..e779908 --- /dev/null +++ b/codemp/renderer/tr_main.cpp @@ -0,0 +1,1631 @@ +// 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 + +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]; + + 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..09d5e3e --- /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..6094588 --- /dev/null +++ b/codemp/renderer/tr_mesh.cpp @@ -0,0 +1,423 @@ +//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" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.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.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 + // +#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 + // + 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 ) { +#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 ); + } + +} + +#endif // !DEDICATED + diff --git a/codemp/renderer/tr_model.cpp b/codemp/renderer/tr_model.cpp new file mode 100644 index 0000000..2a0f2f3 --- /dev/null +++ b/codemp/renderer/tr_model.cpp @@ -0,0 +1,1838 @@ +//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; + + +#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 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; + int iPAKFileCheckSum; // else -1 if not from PAK + + + CachedEndianedModelBinary_s() + { + pModelDiskImage = 0; + iAllocSize = 0; + ShaderRegisterData.clear(); + iLastLevelUsedOn = -1; + iPAKFileCheckSum = -1; + } +}; +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; +void *RE_RegisterModels_Malloc(int iSize, void *pvDiskBufferIfJustLoaded, const char *psModelFileName, qboolean *pqbAlreadyFound, memtag_t eTag) +{ + char sModelName[MAX_QPATH]; + + assert(CachedModels); + + 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 + // + if ( pvDiskBufferIfJustLoaded ) + { + Z_MorphMallocTag( pvDiskBufferIfJustLoaded, eTag ); + } + 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 + { +#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) +{ + 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 + // + if ( pvDiskBufferIfJustLoaded ) + { + Z_MorphMallocTag( pvDiskBufferIfJustLoaded, eTag ); + } + 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) + { + 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) { + 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(); + } + } + } + + 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) +// +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) { + 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"); +} + +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) { + Z_Free(CachedModel.pModelDiskImage); + } + + itModel = CachedModels->erase(itModel); + } +#else + CachedModels->erase(CachedModels->begin(),CachedModels->end()); +#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 ) { + 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 = (struct model_s *)Hunk_Alloc( sizeof( *tr.models[tr.numModels] ), h_low ); + 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((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; +} +/* +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); + + 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... + // + bAlreadyCached = qtrue; + assert( mdxa == buffer ); +// memcpy( mdxa, buffer, size ); // and don't do this now, since it's the same thing + + 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); + + 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... + // + bAlreadyCached = qtrue; + assert( mdxm == buffer ); +// memcpy( mdxm, buffer, size ); // and don't do this now, since it's the same thing + + 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; + } + } + + 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; +} + + +/* +==================== +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; + } + } + +// 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 ); + } + + 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; + } + } + } + + 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; + mod->md3[lod] = (md3Header_t *) //Hunk_Alloc( size ); + RE_RegisterModels_Malloc(size, buffer, mod_name, &bAlreadyFound, TAG_MODEL_MD3); + + 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... + // + bAlreadyCached = qtrue; + assert( mod->md3[lod] == buffer ); +// memcpy( mod->md3[lod], buffer, size ); // and don't do this now, since it's the same thing + + 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; + memset(mhHashTable, 0, sizeof(mhHashTable)); + + 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(mhHashTable, 0, sizeof(mhHashTable)); + tr.numShaders = 0; + tr.numSkins = 0; +} + +void R_ModelFree(void) +{ + if(CachedModels) { + RE_RegisterModels_DeleteAll(); + delete CachedModels; + CachedModels = 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 < tr.numModels; i++ ) { + 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..879a5df --- /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..60dfecb --- /dev/null +++ b/codemp/renderer/tr_public.h @@ -0,0 +1,118 @@ +#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 ); +#ifndef VV_LIGHTING + 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 ); +#endif + 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..f510efc --- /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..37d5691 --- /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..79deccf --- /dev/null +++ b/codemp/renderer/tr_scene.cpp @@ -0,0 +1,886 @@ +//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 VV_LIGHTING +#include "tr_lightmanager.h" +#endif + +#pragma warning (disable: 4512) //default assignment operator could not be gened +#include "../qcommon/disablewarnings.h" + +static int r_firstSceneDrawSurf; + +static int r_numdlights; +static 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; + +#ifdef VV_LIGHTING + VVLightMan.num_dlights = 0; +#endif + 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)); + 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 + +===================== +*/ +#ifndef VV_LIGHTING +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; +} +#endif + +/* +===================== +RE_AddLightToScene + +===================== +*/ +#ifndef VV_LIGHTING +void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, qfalse ); +} +#endif + +/* +===================== +RE_AddAdditiveLightToScene + +===================== +*/ +#ifndef VV_LIGHTING +void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + RE_AddDynamicLightToScene( org, intensity, r, g, b, qtrue ); +} +#endif + + +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]; + +#ifndef VV_LIGHTING + tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight; + tr.refdef.dlights = &backEndData->dlights[r_firstSceneDlight]; +#endif + + // 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 +#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 + // + 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; + 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..f8174a4 --- /dev/null +++ b/codemp/renderer/tr_shade.cpp @@ -0,0 +1,2475 @@ +//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 "tr_lightmanager.h" +#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; + + 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(vec3_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); + + 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); + + 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); + + 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 && 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 (!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 = 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: +#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 ( pStage->alphaGen ) + { + 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..ccc2c0e --- /dev/null +++ b/codemp/renderer/tr_shade_calc.cpp @@ -0,0 +1,1671 @@ +//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 ) { + +} +#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 ) { + +} +#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 ) { + +} +#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 +*/ +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]; + } +} + +//--------------------------------------------------------- +#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..d992968 --- /dev/null +++ b/codemp/renderer/tr_shader.cpp @@ -0,0 +1,4283 @@ +//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; + +// 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 int lightmapsNone[MAXLIGHTMAPS] = +{ + LIGHTMAP_NONE, + LIGHTMAP_NONE, + LIGHTMAP_NONE, + LIGHTMAP_NONE +}; + +const int lightmaps2d[MAXLIGHTMAPS] = +{ + LIGHTMAP_2D, + LIGHTMAP_2D, + LIGHTMAP_2D, + LIGHTMAP_2D +}; + +const int lightmapsVertex[MAXLIGHTMAPS] = +{ + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX +}; + +const int 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 int *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, "(" ) ) { + Com_Printf (S_COLOR_YELLOW "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] ) { + 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, ")" ) ) { + Com_Printf (S_COLOR_YELLOW "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; + } + + 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, (qboolean)!shader.noPicMip, (qboolean)!shader.noTC, 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, !shader.noPicMip, !shader.noTC, 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, (qboolean)!shader.noPicMip, (qboolean)!shader.noTC, 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, (qboolean)!shader.noPicMip, (qboolean)!shader.noTC, 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, !shader.noPicMip, 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; + } + 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 ) + { + 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, (qboolean)!shader.noTC, 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" ) ) { + 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--; + } + + 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; + } + } + + p = s_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 int *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 int *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(); +} + +static void ScanAndLoadShaderFiles( const char *path ); +shader_t *R_FindServerShader( const char *name, const int *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, int *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 int *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 +static void ScanAndLoadShaderFiles( const char *path ) +{ + char **shaderFiles; + char *buffers[MAX_SHADER_FILES]; + char *p; + int numShaders; + int i; + char *oldp, *token, *hashMem; + int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; + + 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 += 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 ); + + // free in reverse order, so the temp files are all dumped + for ( i = numShaders - 1; i >= 0 ; i-- ) { + strcat( s_shaderText, "\n" ); + strcat( s_shaderText, buffers[i] ); + /*ri.*/FS_FreeFile( (void*) buffers[i] ); + } + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + size = 0; + p = s_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 = s_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"); + + CreateExternalShaders(); + } +#endif +} diff --git a/codemp/renderer/tr_shadows.cpp b/codemp/renderer/tr_shadows.cpp new file mode 100644 index 0000000..13f86b4 --- /dev/null +++ b/codemp/renderer/tr_shadows.cpp @@ -0,0 +1,709 @@ +//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] + +*/ + +#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; + } +} + +//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; + } + + qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, cX, cY, radX, radY, 0); +} + +//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); + } + + qglBegin(GL_QUADS); + 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; + + qglBegin(GL_QUADS); + 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..5a3534f --- /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..4ce36ef --- /dev/null +++ b/codemp/renderer/tr_surface.cpp @@ -0,0 +1,2033 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// tr_surf.c +#include "tr_local.h" + +/* + + 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; + + 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*/; + + 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; + + 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: + { + 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 +================== +*/ +static bool RB_TestZFlare( vec3_t point) { + 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 + float depth = 0.0f; + bool visible; + float screenZ; + + // read back the z buffer contents +#ifdef _XBOX + depth = 0.0f; +#else + 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 ); +#endif + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -eye[2] - -screenZ ) < 24; + return visible; +} + +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; + + if (!RB_TestZFlare( sorigin) ) { + 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*))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..0725c4e --- /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..db51b61 --- /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..370bd1b --- /dev/null +++ b/codemp/renderer/tr_world.cpp @@ -0,0 +1,1959 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "tr_local.h" + +#ifdef VV_LIGHTING +#include "tr_lightmanager.h" +#endif + +#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; +} + +#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; +} + +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; +} +#endif // VV_LIGHTING + + + +#ifdef _ALT_AUTOMAP_METHOD +static bool tr_drawingAutoMap = false; +#endif +static float g_playerHeight = 0.0f; + +/* +====================== +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 (!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 ) { +#ifdef VV_LIGHTING + dlightBits = VVLightMan.R_DlightSurface( surf, dlightBits ); +#else + dlightBits = R_DlightSurface( surf, dlightBits ); +#endif + 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 +#ifdef VV_LIGHTING + VVLightMan.R_SetupEntityLighting(&tr.refdef, ent); +#else + R_SetupEntityLighting(&tr.refdef, ent); +#endif + } + + //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 +#ifdef VV_LIGHTING + VVLightMan.R_DlightBmodel( bmodel, false ); +#else + R_DlightBmodel( bmodel, false ); +#endif +// } +// 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 +================ +*/ +#ifndef VV_LIGHTING +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, 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( 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 ( 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, 15, ( 1 << tr.refdef.num_dlights ) - 1 ); +} +#endif // _XBOX diff --git a/codemp/renderer/vssver.scc b/codemp/renderer/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..6f5404c3eaa368e338db1fc4bdbab89b1dab8d6e GIT binary patch literal 752 zcmW;Kdq`7J90%|%EwwUr+LlV9O(iLNz^o9*)C#2(5}d7bcAX}XVUZC%mY8oGm`O`2 zmpx3UGEJK{Gp(kAN>GrQIWo;hC?w?)%SaY|&%J-#%jcfo`JMAS=O+^h1sD58C(33! z57dnZs{_)P8$bQ7`fCsh_~$OZnzYTLl}2Y3`&EtC{e^~!q7|9S3BCtQGRk)-6bjWU z_yL?eofkD_eoxoHkKn4$O3ChqIm+T|;kP-awEgw@Gy<-J<=J1RZYnFO2W*5>9Al~B zM=jJ9u7}IIGryg!jit_T1Diiwn(QS@rffYEY%?qde0LI33OB*$-W>Kjb;Xx%g_~hd zzYERW*ghHuKZe!hTA#x)P1eFKaOC`e)#B?QF>ot9@vvyQWWAn<;V1C4S*<=}sUmFu zX85-Ik#(&FM#AcAgN4dH#Yuiv;t99ITD^Mkq&%9i{XK=PKQtjVZVAKW=N^S5M5!+twAQm|;YfnkkYUoBd2%Zd;-Ksd4#ozA$E+yNh@4Znd=ld(q zMs.number, pmins, pmaxs, point, clipmask, okToHitEntNum); +} + +qboolean GNavCallback_NPC_ClearLOS( sharedEntity_t *ent, const vec3_t end ) +{ + return (qboolean)VM_Call(gvm, GAME_NAV_CLEARLOS, ent->s.number, end); +} + +int GNavCallback_NAVNEW_ClearPathBetweenPoints(vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int ignore, int clipmask) +{ + return VM_Call(gvm, GAME_NAV_CLEARPATHBETWEENPOINTS, start, end, mins, maxs, ignore, clipmask); +} + +qboolean GNavCallback_NAV_CheckNodeFailedForEnt( sharedEntity_t *ent, int nodeNum ) +{ + return (qboolean)VM_Call(gvm, GAME_NAV_CHECKNODEFAILEDFORENT, ent->s.number, nodeNum); +} + +qboolean GNavCallback_G_EntIsUnlockedDoor( int entityNum ) +{ + return (qboolean)VM_Call(gvm, GAME_NAV_ENTISUNLOCKEDDOOR, entityNum); +} + +qboolean GNavCallback_G_EntIsDoor( int entityNum ) +{ + return (qboolean)VM_Call(gvm, GAME_NAV_ENTISDOOR, entityNum); +} + +qboolean GNavCallback_G_EntIsBreakable( int entityNum ) +{ + return (qboolean)VM_Call(gvm, GAME_NAV_ENTISBREAKABLE, entityNum); +} + +qboolean GNavCallback_G_EntIsRemovableUsable( int entNum ) +{ + return (qboolean)VM_Call(gvm, GAME_NAV_ENTISREMOVABLEUSABLE, entNum); +} + +void GNavCallback_CP_FindCombatPointWaypoints( void ) +{ + VM_Call(gvm, GAME_NAV_FINDCOMBATPOINTWAYPOINTS); +} diff --git a/codemp/server/NPCNav/navigator.cpp b/codemp/server/NPCNav/navigator.cpp new file mode 100644 index 0000000..eb0c891 --- /dev/null +++ b/codemp/server/NPCNav/navigator.cpp @@ -0,0 +1,2783 @@ +#include "../../game/q_shared.h" + +#pragma warning( disable : 4018) +#pragma warning( disable : 4245) +#pragma warning( disable : 4284) +#pragma warning( disable : 4512) +#pragma warning( disable : 4786) + +#pragma warning ( disable : 4663 ) //spcialize class +#pragma warning( push, 3 ) +#include +#pragma warning (pop) + +#include "navigator.h" +#include "../../game/g_nav.h" +#include +#ifdef __linux__ +DWORD timeGetTime(void); +#endif + +extern qboolean GNavCallback_NAV_ClearPathToPoint( sharedEntity_t *self, vec3_t pmins, vec3_t pmaxs, vec3_t point, int clipmask, int okToHitEntNum ); +extern qboolean GNavCallback_NPC_ClearLOS( sharedEntity_t *ent, const vec3_t end ); +extern int GNavCallback_NAVNEW_ClearPathBetweenPoints(vec3_t start, vec3_t end, vec3_t mins, vec3_t maxs, int ignore, int clipmask); +extern qboolean GNavCallback_NAV_CheckNodeFailedForEnt( sharedEntity_t *ent, int nodeNum ); +extern qboolean GNavCallback_G_EntIsUnlockedDoor( int entityNum ); +extern qboolean GNavCallback_G_EntIsDoor( int entityNum ); +extern qboolean GNavCallback_G_EntIsBreakable( int entityNum ); +extern qboolean GNavCallback_G_EntIsRemovableUsable( int entNum ); +extern void GNavCallback_CP_FindCombatPointWaypoints( void ); + +//Global navigator +CNavigator navigator; +//this was in the game before. But now it's all handled in the engine, and we make navigator. calls +//via traps. + +cvar_t *d_altRoutes; +cvar_t *d_patched; + +void NAV_CvarInit() +{ + d_altRoutes = Cvar_Get("d_altRoutes", "0", CVAR_CHEAT); + d_patched = Cvar_Get("d_patched", "0", CVAR_CHEAT); +} + +void NAV_Free() +{ + navigator.Free(); +} + +static vec3_t wpMaxs = { 16, 16, 32 }; +static vec3_t wpMins = { -16, -16, -24+STEPSIZE };//WTF: was 16??!!! + + +static byte CHECKED_NO = 0; +static byte CHECKED_FAILED = 1; +static byte CHECKED_PASSED = 2; + + +#if AI_TIMERS +int GetTime ( int lastTime ) +{ + int curtime; + static int timeBase = 0; + static qboolean initialized = qfalse; + + if (!initialized) { + timeBase = timeGetTime(); + initialized = qtrue; + } + curtime = timeGetTime() - timeBase - lastTime; + + return curtime; +} +#endif + +/* +------------------------- +CEdge +------------------------- +*/ + +CEdge::CEdge( void ) +{ + CEdge( -1, -1, -1 ); +} + +CEdge::CEdge( int first, int second, int cost ) +{ + m_first = first; + m_second = second; + m_cost = cost; +} + +CEdge::~CEdge( void ) +{ +} + +/* +------------------------- +CNode +------------------------- +*/ + +CNode::CNode( void ) +{ + m_numEdges = 0; + m_radius = 0; + m_ranks = NULL; +} + +CNode::~CNode( void ) +{ + m_edges.clear(); + + if ( m_ranks ) + delete [] m_ranks; +} + +/* +------------------------- +Create +------------------------- +*/ + +CNode *CNode::Create( vec3_t position, int flags, int radius, int ID ) +{ + CNode *node = new CNode; + + VectorCopy( position, node->m_position ); + + node->m_flags = flags; + node->m_ID = ID; + node->m_radius = radius; + + return node; +} + +/* +------------------------- +Create +------------------------- +*/ + +CNode *CNode::Create( void ) +{ + return new CNode; +} + +/* +------------------------- +AddEdge +------------------------- +*/ + +void CNode::AddEdge( int ID, int cost, int flags ) +{ + if ( m_numEdges ) + {//already have at least 1 + //see if it exists already + edge_v::iterator ei; + STL_ITERATE( ei, m_edges ) + { + if ( (*ei).ID == ID ) + {//found it + (*ei).cost = cost; + (*ei).flags = flags; + return; + } + } + } + + edge_t edge; + + edge.ID = ID; + edge.cost = cost; + edge.flags = flags; + + STL_INSERT( m_edges, edge ); + + m_numEdges++; + + assert( m_numEdges < 9 );//8 is the max +} + +/* +------------------------- +GetEdge +------------------------- +*/ + +int CNode::GetEdgeNumToNode( int ID ) +{ + int count = 0; + + edge_v::iterator ei; + STL_ITERATE( ei, m_edges ) + { + if ( (*ei).ID == ID ) + { + return count; + } + + count++; + } + return -1; +} + +/* +------------------------- +AddRank +------------------------- +*/ + +void CNode::AddRank( int ID, int rank ) +{ + assert( m_ranks ); + + m_ranks[ ID ] = rank; +} + +/* +------------------------- +Draw +------------------------- +*/ + +void CNode::Draw( qboolean showRadius ) +{ //rwwFIXMEFIXME: ... + /* + CG_DrawNode( m_position, NODE_NORMAL ); + if( showRadius ) + { + CG_DrawRadius( m_position, m_radius, NODE_NORMAL ); + } + */ +} + +/* +------------------------- +GetEdge +------------------------- +*/ + +int CNode::GetEdge( int edgeNum ) +{ + if ( edgeNum > m_numEdges ) + return -1; + + int count = 0; + + edge_v::iterator ei; + STL_ITERATE( ei, m_edges ) + { + if ( count == edgeNum ) + { + return (*ei).ID; + } + + count++; + } + + return -1; +} + +/* +------------------------- +GetEdgeCost +------------------------- +*/ + +int CNode::GetEdgeCost( int edgeNum ) +{ + if ( edgeNum > m_numEdges ) + return Q3_INFINITE; // return -1; + + int count = 0; + + edge_v::iterator ei; + STL_ITERATE( ei, m_edges ) + { + if ( count == edgeNum ) + { + return (*ei).cost; + } + + count++; + } + + return Q3_INFINITE; // return -1; +} + +/* +------------------------- +GetEdgeFlags +------------------------- +*/ + +BYTE CNode::GetEdgeFlags( int edgeNum ) +{ + if ( edgeNum > m_numEdges ) + return 0; + + int count = 0; + + edge_v::iterator ei; + STL_ITERATE( ei, m_edges ) + { + if ( count == edgeNum ) + { + return (*ei).flags; + } + + count++; + } + + return 0; +} + +/* +------------------------- +SetEdgeFlags +------------------------- +*/ + +void CNode::SetEdgeFlags( int edgeNum, int newFlags ) +{ + if ( edgeNum > m_numEdges ) + return; + + int count = 0; + + edge_v::iterator ei; + STL_ITERATE( ei, m_edges ) + { + if ( count == edgeNum ) + { + (*ei).flags = newFlags; + return; + } + + count++; + } + +} +/* +------------------------- +InitRanks +------------------------- +*/ + +void CNode::InitRanks( int size ) +{ + //Clear it if it's already allocated + if ( m_ranks != NULL ) + { + delete [] m_ranks; + m_ranks = NULL; + } + + m_ranks = new int[size]; + + memset( m_ranks, -1, sizeof(int)*size ); +} + +/* +------------------------- +GetRank +------------------------- +*/ + +int CNode::GetRank( int ID ) +{ + assert( m_ranks ); + + return m_ranks[ ID ]; +} + + +/* +------------------------- +Save +------------------------- +*/ + +int CNode::Save( int numNodes, fileHandle_t file ) +{ + //Write out the header + unsigned long header = NODE_HEADER_ID; + FS_Write( &header, sizeof( header ), file ); + + //Write out the basic information + int i; + for ( i = 0; i < 3; i++ ) + FS_Write( &m_position[i], sizeof( float ), file ); + + FS_Write( &m_flags, sizeof( m_flags ), file ); + FS_Write( &m_ID, sizeof( m_ID ), file ); + FS_Write( &m_radius, sizeof( m_radius ), file ); + + //Write out the edge information + FS_Write( &m_numEdges, sizeof( m_numEdges ), file ); + + edge_v::iterator ei; + STL_ITERATE( ei, m_edges ) + { + FS_Write( &(*ei), sizeof( edge_t ), file ); + } + + //Write out the node ranks + FS_Write( &numNodes, sizeof( numNodes ), file ); + + for ( i = 0; i < numNodes; i++ ) + { + FS_Write( &m_ranks[i], sizeof( int ), file ); + } + + return true; +} + +/* +------------------------- +Load +------------------------- +*/ + +int CNode::Load( int numNodes, fileHandle_t file ) +{ + unsigned long header; + FS_Read( &header, sizeof(header), file ); + + //Validate the header + if ( header != NODE_HEADER_ID ) + return false; + + //Get the basic information + int i; + for ( i = 0; i < 3; i++ ) + FS_Read( &m_position[i], sizeof( float ), file ); + + FS_Read( &m_flags, sizeof( m_flags ), file ); + FS_Read( &m_ID, sizeof( m_ID ), file ); + FS_Read( &m_radius, sizeof( m_radius ), file ); + + //Get the edge information + FS_Read( &m_numEdges, sizeof( m_numEdges ), file ); + + for ( i = 0; i < m_numEdges; i++ ) + { + edge_t edge; + + FS_Read( &edge, sizeof( edge_t ), file ); + + STL_INSERT( m_edges, edge ); + } + + //Read the node ranks + int numRanks; + + FS_Read( &numRanks, sizeof( numRanks ), file ); + + //Allocate the memory + InitRanks( numRanks ); + + for ( i = 0; i < numRanks; i++ ) + { + FS_Read( &m_ranks[i], sizeof( int ), file ); + } + + return true; +} + +/* +------------------------- +CNavigator +------------------------- +*/ + +CNavigator::CNavigator( void ) +{ + if (!d_altRoutes || !d_patched) + { + NAV_CvarInit(); + } +} + +CNavigator::~CNavigator( void ) +{ +} + +/* +------------------------- +FlagAllNodes +------------------------- +*/ + +void CNavigator::FlagAllNodes( int newFlag ) +{ + node_v::iterator ni; + + STL_ITERATE( ni, m_nodes ) + { + (*ni)->AddFlag( newFlag ); + } +} + +/* +------------------------- +GetChar +------------------------- +*/ + +char CNavigator::GetChar( fileHandle_t file ) +{ + char value; + + FS_Read( &value, sizeof( value ), file ); + + return value; +} + +/* +------------------------- +GetInt +------------------------- +*/ + +int CNavigator::GetInt( fileHandle_t file ) +{ + int value; + + FS_Read( &value, sizeof( value ), file ); + + return value; +} + +/* +------------------------- +GetFloat +------------------------- +*/ + +float CNavigator::GetFloat( fileHandle_t file ) +{ + float value; + + FS_Read( &value, sizeof( value ), file ); + + return value; +} + +/* +------------------------- +GetLong +------------------------- +*/ + +long CNavigator::GetLong( fileHandle_t file ) +{ + long value; + + FS_Read( &value, sizeof( value ), file ); + + return value; +} + +/* +------------------------- +Init +------------------------- +*/ + +void CNavigator::Init( void ) +{ + Free(); +} + +/* +------------------------- +Free +------------------------- +*/ + +void CNavigator::Free( void ) +{ + node_v::iterator ni; + + STL_ITERATE( ni, m_nodes ) + { + delete (*ni); + } + + m_nodes.clear(); + m_edgeLookupMap.clear(); +} + +/* +------------------------- +Load +------------------------- +*/ + +bool CNavigator::Load( const char *filename, int checksum ) +{ + fileHandle_t file; + + //Attempt to load the file + FS_FOpenFileByMode( va( "maps/%s.nav", filename ), &file, FS_READ ); + + //See if we succeeded + if ( file == NULL ) + return false; + + //Check the header id + long navID = GetLong( file ); + + if ( navID != NAV_HEADER_ID ) + { + FS_FCloseFile( file ); + return false; + } + + //Check the checksum to see if this file is out of date + int check = GetInt( file ); + + if ( check != checksum ) + { + FS_FCloseFile( file ); + return false; + } + + int numNodes = GetInt( file ); + + for ( int i = 0; i < numNodes; i++ ) + { + CNode *node = CNode::Create(); + + if ( node->Load( numNodes, file ) == false ) + { + FS_FCloseFile( file ); + return false; + } + + STL_INSERT( m_nodes, node ); + } + + //read in the failed edges + FS_Read( &failedEdges, sizeof( failedEdges ), file ); + for ( int j = 0; j < MAX_FAILED_EDGES; j++ ) + { + m_edgeLookupMap.insert(pair(failedEdges[j].startID, j)); + } + + + FS_FCloseFile( file ); + + return true; +} + +/* +------------------------- +Save +------------------------- +*/ + +bool CNavigator::Save( const char *filename, int checksum ) +{ + fileHandle_t file; + + //Attempt to load the file + FS_FOpenFileByMode( va( "maps/%s.nav", filename ), &file, FS_WRITE ); + + if ( file == NULL ) + return false; + + //Write out the header id + unsigned long id = NAV_HEADER_ID; + + FS_Write( &id, sizeof (id), file ); + + //Write out the checksum + FS_Write( &checksum, sizeof( checksum ), file ); + + int numNodes = m_nodes.size(); + + //Write out the number of nodes to follow + FS_Write( &numNodes, sizeof(numNodes), file ); + + //Write out all the nodes + node_v::iterator ni; + + STL_ITERATE( ni, m_nodes ) + { + (*ni)->Save( numNodes, file ); + } + + //write out failed edges + FS_Write( &failedEdges, sizeof( failedEdges ), file ); + + FS_FCloseFile( file ); + + return true; +} + +/* +------------------------- +AddRawPoint +------------------------- +*/ + +int CNavigator::AddRawPoint( vec3_t point, int flags, int radius ) +{ + CNode *node = CNode::Create( point, flags, radius, m_nodes.size() ); + + if ( node == NULL ) + { + Com_Error( ERR_DROP, "Error adding node!\n" ); + return -1; + } + + //TODO: Validate the position + //TODO: Correct stuck waypoints + + STL_INSERT( m_nodes, node ); + + return node->GetID(); +} + +/* +------------------------- +GetEdgeCost +------------------------- +*/ + +int CNavigator::GetEdgeCost( CNode *first, CNode *second ) +{ + trace_t trace; + vec3_t start, end; + vec3_t mins, maxs; + + //Setup the player size + VectorSet( mins, -8, -8, -8 ); + VectorSet( maxs, 8, 8, 8 ); + + //Setup the points + first->GetPosition( start ); + second->GetPosition( end ); + + SV_Trace( &trace, start, mins, maxs, end, ENTITYNUM_NONE, MASK_SOLID, qfalse, 0, 10 ); + + if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid ) + return Q3_INFINITE; // return -1; + + //Connection successful, return the cost + return Distance( start, end ); +} + +void CNavigator::SetEdgeCost( int ID1, int ID2, int cost ) +{ + if( (ID1 == -1) || (ID2 == -1) ) + {//not valid nodes, must have come from the ClearAllFailedEdges initization-type calls + return; + } + + CNode *node1 = m_nodes[ID1]; + CNode *node2 = m_nodes[ID2]; + + if ( cost == -1 ) + {//they want us to calc it + //FIXME: can we just remember this instead of recalcing every time? + vec3_t pos1, pos2; + + node1->GetPosition( pos1 ); + node2->GetPosition( pos2 ); + cost = Distance( pos1, pos2 ); + } + + //set it + node1->AddEdge( ID2, cost ); + node2->AddEdge( ID1, cost ); +} + +/* +------------------------- +AddNodeEdges +------------------------- +*/ + +void CNavigator::AddNodeEdges( CNode *node, int addDist, edge_l &edgeList, bool *checkedNodes ) +{ + //Add all edge + for ( int i = 0; i < node->GetNumEdges(); i++ ) + { + //Make sure we don't add an old edge twice + if ( checkedNodes[ node->GetEdge( i ) ] == true ) + continue; + + //Get the node + CNode *nextNode = m_nodes[ node->GetEdge( i ) ]; + + //This node has now been checked + checkedNodes[ nextNode->GetID() ] = true; + + //Add it to the list + STL_INSERT( edgeList, CEdge( nextNode->GetID(), node->GetID(), addDist + ( node->GetEdgeCost( i ) ) ) ); + } +} + +/* +------------------------- +CalculatePath +------------------------- +*/ + +void CNavigator::CalculatePath( CNode *node ) +{ + int curRank = 0; + + CPriorityQueue *pathList = new CPriorityQueue(); + BYTE *checked; + + //Init the completion table + checked = new BYTE[ m_nodes.size() ]; + memset( checked, 0, m_nodes.size() ); + + //Mark this node as checked + checked[ node->GetID() ] = true; + node->AddRank( node->GetID(), curRank++ ); + + //Add all initial nodes + int i; + for ( i = 0; i < node->GetNumEdges(); i++ ) + { + CNode *nextNode = m_nodes[ node->GetEdge(i) ]; + assert(nextNode); + + checked[ nextNode->GetID() ] = true; + + pathList->Push( new CEdge( nextNode->GetID(), nextNode->GetID(), node->GetEdgeCost(i) ) ); + } + + float minDist; + CEdge *test; + + //Now flood fill all the others + while ( !pathList->Empty() ) + { + minDist = Q3_INFINITE; + test = pathList->Pop(); + + CNode *testNode = m_nodes[ (*test).m_first ]; + assert( testNode ); + + node->AddRank( testNode->GetID(), curRank++ ); + + //Add in all the new edges + for ( i = 0; i < testNode->GetNumEdges(); i++ ) + { + CNode *addNode = m_nodes[ testNode->GetEdge(i) ]; + assert( addNode ); + + if ( checked[ addNode->GetID() ] ) + continue; + + int newDist = (*test).m_cost + testNode->GetEdgeCost(i); + pathList->Push( new CEdge( addNode->GetID(), (*test).m_second, newDist ) ); + + checked[ addNode->GetID() ] = true; + } + delete test; + + } + + node->RemoveFlag( NF_RECALC ); + + delete pathList; + delete [] checked; +} + +/* +------------------------- +CalculatePaths +------------------------- +*/ +void CNavigator::CalculatePaths( qboolean recalc ) +{ +#if _HARD_CONNECT +#else +#endif + + int i; + for ( i = 0; i < m_nodes.size(); i++ ) + { + //Allocate the needed memory + m_nodes[i]->InitRanks( m_nodes.size() ); + } + + for ( i = 0; i < m_nodes.size(); i++ ) + { + CalculatePath( m_nodes[i] ); + } + + if(!recalc) //Mike says doesn't need to happen on recalc + { + GNavCallback_CP_FindCombatPointWaypoints(); + } + + pathsCalculated = qtrue; +} + +/* +------------------------- +ShowNodes +------------------------- +*/ + +void CNavigator::ShowNodes( void ) +{ + node_v::iterator ni; + + vec3_t position; + qboolean showRadius; + float dist, + radius; + + STL_ITERATE( ni, m_nodes ) + { + (*ni)->GetPosition( position ); + + showRadius = qfalse; + //if( NAVDEBUG_showRadius ) + if (0) + { + dist = DistanceSquared( SV_GentityNum(0)->r.currentOrigin, position ); + radius = (*ni)->GetRadius(); + // if player within node radius or 256, draw radius (sometimes the radius is really small, so we check for 256 to catch everything) + if( (dist <= radius*radius) || dist <= 65536 ) + { + showRadius = qtrue; + } + } + else + { + dist = DistanceSquared( SV_GentityNum(0)->r.currentOrigin, position ); + } + if ( dist < 1048576 ) + { + if ( SV_inPVS( SV_GentityNum(0)->r.currentOrigin, position ) ) + { + (*ni)->Draw(showRadius); + } + } + } +} + +/* +------------------------- +ShowEdges +------------------------- +*/ + +typedef map < int, bool > drawMap_m; + +void CNavigator::ShowEdges( void ) +{ + node_v::iterator ni; + vec3_t start, end; + + drawMap_m *drawMap; + + drawMap = new drawMap_m[ m_nodes.size() ]; + + STL_ITERATE( ni, m_nodes ) + { + (*ni)->GetPosition( start ); + if ( DistanceSquared( SV_GentityNum(0)->r.currentOrigin, start ) >= 1048576 ) + { + continue; + } + + if (!SV_inPVS(SV_GentityNum(0)->r.currentOrigin, start)) + { + continue; + } + + //Attempt to draw each connection + for ( int i = 0; i < (*ni)->GetNumEdges(); i++ ) + { + int id = (*ni)->GetEdge( i ); + + if ( id == -1 ) + continue; + + //Already drawn? + if ( drawMap[(*ni)->GetID()].find( id ) != drawMap[(*ni)->GetID()].end() ) + continue; + + //BYTE flags = (*ni)->GetEdgeFlags( i ); + + CNode *node = m_nodes[id]; + + node->GetPosition( end ); + + //Set this as drawn + drawMap[id][(*ni)->GetID()] = true; + + if ( DistanceSquared( SV_GentityNum(0)->r.currentOrigin, end ) >= 1048576 ) + { + continue; + } + + if ( !SV_inPVS( SV_GentityNum(0)->r.currentOrigin, end ) ) + continue; + + /* + if ( EdgeFailed( id, (*ni)->GetID() ) != -1 )//flags & EFLAG_FAILED ) + CG_DrawEdge( start, end, EDGE_FAILED ); + else if ( flags & EFLAG_BLOCKED ) + CG_DrawEdge( start, end, EDGE_BLOCKED ); + else + CG_DrawEdge( start, end, EDGE_NORMAL ); + */ + //rwwFIXMEFIXME: ... + } + } + + delete [] drawMap; +} + +int CNavigator::GetNodeRadius( int nodeID ) +{ + if ( m_nodes.size() == 0 ) + return 0; + return m_nodes[nodeID]->GetRadius(); +} + +void CNavigator::CheckBlockedEdges( void ) +{ + CNode *start, *end; + vec3_t p1, p2; + int flags, first, second; + trace_t trace; + qboolean failed; + int edgeNum; + node_v::iterator ni; + + //Go through all edges and test the ones that were blocked + STL_ITERATE( ni, m_nodes ) + { + //Attempt to draw each connection + for ( edgeNum = 0; edgeNum < (*ni)->GetNumEdges(); edgeNum++ ) + { + flags = (*ni)->GetEdgeFlags( edgeNum ); + if ( (flags&EFLAG_BLOCKED) ) + { + first = (*ni)->GetID(); + second = (*ni)->GetEdge( edgeNum ); + start = m_nodes[first]; + end = m_nodes[second]; + failed = qfalse; + + start->GetPosition( p1 ); + end->GetPosition( p2 ); + + //FIXME: can't we just store the trace.entityNum from the HardConnect trace? So we don't have to do another trace here... + SV_Trace( &trace, p1, wpMins, wpMaxs, p2, ENTITYNUM_NONE, MASK_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, qfalse, 0, 10 ); + + if ( trace.entityNum < ENTITYNUM_WORLD && (trace.fraction < 1.0f || trace.startsolid == qtrue || trace.allsolid == qtrue) ) + {//could be assumed, since failed before + if ( GNavCallback_G_EntIsDoor( trace.entityNum ) ) + {//door + if ( !GNavCallback_G_EntIsUnlockedDoor( trace.entityNum ) ) + {//locked door + failed = qtrue; + } + } + else + { + if ( GNavCallback_G_EntIsBreakable( trace.entityNum ) ) + {//do same for breakable brushes/models/glass? + failed = qtrue; + } + else if ( GNavCallback_G_EntIsRemovableUsable( trace.entityNum ) ) + { + failed = qtrue; + } + else if ( trace.allsolid || trace.startsolid ) + {//FIXME: the entitynum would be none here, so how do we know if this is stuck inside an ent or the world? + } + else + {//FIXME: what about func_plats and scripted movers? + } + } + } + + if ( failed ) + { + //could add the EFLAG_FAILED to the two edges, but we stopped doing that since it was pointless + AddFailedEdge( ENTITYNUM_NONE, first, second ); + } + } + } + } +} + +#if _HARD_CONNECT + +/* +------------------------- +HardConnect +------------------------- +*/ + +void CNavigator::HardConnect( int first, int second ) +{ + CNode *start, *end; + + start = m_nodes[first]; + end = m_nodes[second]; + + vec3_t p1, p2; + + start->GetPosition( p1 ); + end->GetPosition( p2 ); + + trace_t trace; + + int flags = EFLAG_NONE; + + SV_Trace( &trace, p1, wpMins, wpMaxs, p2, ENTITYNUM_NONE, MASK_SOLID|CONTENTS_BOTCLIP|CONTENTS_MONSTERCLIP, qfalse, 0, 10 ); + + int cost = Distance( p1, p2 ); + + if ( trace.fraction != 1.0f || trace.startsolid == qtrue || trace.allsolid == qtrue ) + { + flags |= EFLAG_BLOCKED; + } + + start->AddEdge( second, cost, flags ); + end->AddEdge( first, cost, flags ); +} + +#endif + +/* +------------------------- +TestNodePath +------------------------- +*/ + +int CNavigator::TestNodePath( sharedEntity_t *ent, int okToHitEntNum, vec3_t position, qboolean includeEnts ) +{ //rwwFIXMEFIXME: Share clipmask? + int clipmask = MASK_SOLID;//ent->clipmask; + + if ( !includeEnts ) + { + clipmask &= ~CONTENTS_BODY; + } + //Check the path + if ( GNavCallback_NAV_ClearPathToPoint( ent, ent->r.mins, ent->r.maxs, position, clipmask, okToHitEntNum ) == false ) + return false; + + return true; +} + +/* +------------------------- +TestNodeLOS +------------------------- +*/ + +int CNavigator::TestNodeLOS( sharedEntity_t *ent, vec3_t position ) +{ + return GNavCallback_NPC_ClearLOS( ent, position ); +} + +/* +------------------------- +TestBestFirst +------------------------- +*/ + +int CNavigator::TestBestFirst( sharedEntity_t *ent, int lastID, int flags ) +{ + //Must be a valid one to begin with + if ( lastID == NODE_NONE ) + return NODE_NONE; + + if ( lastID >= m_nodes.size() ) + return NODE_NONE; + + //Get the info + vec3_t nodePos; + CNode *node = m_nodes[ lastID ]; + CNode *testNode; + int numEdges = node->GetNumEdges(); + float dist; + + node->GetPosition( nodePos ); + + //Setup our last node as our root, and search for a closer one according to its edges + int bestNode = ( TestNodePath( ent, ENTITYNUM_NONE, nodePos, qtrue ) ) ? lastID : NODE_NONE; + float bestDist = ( bestNode == NODE_NONE ) ? Q3_INFINITE : DistanceSquared( ent->r.currentOrigin, nodePos ); + + //Test all these edges first + for ( int i = 0; i < numEdges; i++ ) + { + //Get this node and its distance + testNode = m_nodes[ node->GetEdge(i) ]; + + if ( NodeFailed( ent, testNode->GetID() ) ) + { + continue; + } + + testNode->GetPosition( nodePos ); + + dist = DistanceSquared( ent->r.currentOrigin, nodePos ); + + //Test against current best + if ( dist < bestDist ) + { + //See if this node is valid + if ( CheckedNode(testNode->GetID(),ent->s.number) == CHECKED_PASSED || TestNodePath( ent, ENTITYNUM_NONE, nodePos, qtrue ) ) + { + bestDist = dist; + bestNode = testNode->GetID(); + SetCheckedNode(testNode->GetID(),ent->s.number,CHECKED_PASSED); + } + else + { + SetCheckedNode(testNode->GetID(),ent->s.number,CHECKED_FAILED); + } + } + } + + return bestNode; +} + +/* +------------------------- +CollectNearestNodes +------------------------- +*/ + +#define NODE_COLLECT_MAX 16 //Maximum # of nodes collected at any time +#define NODE_COLLECT_RADIUS 512 //Default radius to search for nodes in +#define NODE_COLLECT_RADIUS_SQR ( NODE_COLLECT_RADIUS * NODE_COLLECT_RADIUS ) + +int CNavigator::CollectNearestNodes( vec3_t origin, int radius, int maxCollect, nodeChain_l &nodeChain ) +{ + node_v::iterator ni; + float dist; + vec3_t position; + int collected = 0; + bool added = false; + + //Get a distance rating for each node in the system + STL_ITERATE( ni, m_nodes ) + { + //If we've got our quota, then stop looking + //Get the distance to the node + (*ni)->GetPosition( position ); + dist = DistanceSquared( position, origin ); + + //Must be within our radius range + if ( dist > (float) ( radius * radius ) ) + continue; + + nodeList_t nChain; + nodeChain_l::iterator nci; + + //Always add the first node + if ( nodeChain.size() == 0 ) + { + nChain.nodeID = (*ni)->GetID(); + nChain.distance = dist; + + nodeChain.insert( nodeChain.begin(), nChain ); + continue; + } + + added = false; + + //Compare it to what we already have + STL_ITERATE( nci, nodeChain ) + { + //If we're less, than this entry, then insert before it + if ( dist < (*nci).distance ) + { + nChain.nodeID = (*ni)->GetID(); + nChain.distance = dist; + + nodeChain.insert( nci, nChain ); + collected = nodeChain.size(); + added = true; + + //If we've hit our collection limit, throw off the oldest one + if ( nodeChain.size() > maxCollect ) + { + nodeChain.pop_back(); + } + + break; + } + } + + //Otherwise, always pad out the collection if possible so we don't miss anything + if ( ( added == false ) && ( nodeChain.size() < maxCollect ) ) + { + nChain.nodeID = (*ni)->GetID(); + nChain.distance = dist; + + nodeChain.insert( nodeChain.end(), nChain ); + } + } + + return collected; +} + +int CNavigator::GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goal, int flags ) +{ + //Must have nodes + if ( m_nodes.size() == 0 ) + return NODE_NONE; + +#define MAX_Z_DELTA 18 + + nodeChain_l nodeChain; + nodeChain_l::iterator nci; + nodeChain_l nodeChain2; + nodeChain_l::iterator nci2; + + //Collect all nodes within a certain radius + CollectNearestNodes( ent->r.currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain ); + CollectNearestNodes( goal->r.currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain2 ); + + vec3_t position; + vec3_t position2; + int radius; + int cost, pathCost, bestCost = Q3_INFINITE; + CNode *node, *node2; + int nodeNum, nodeNum2; + int nextNode = NODE_NONE, bestNode = NODE_NONE; + int nodeFlags = 0; +// bool recalc = false; + + ent->waypoint = NODE_NONE; + goal->waypoint = NODE_NONE; + + //Look through all nodes + STL_ITERATE( nci, nodeChain ) + { + node = m_nodes[(*nci).nodeID]; + nodeNum = (*nci).nodeID; + + node->GetPosition( position ); + + if ( CheckedNode(nodeNum,ent->s.number) == CHECKED_FAILED ) + {//already checked this node against ent and it failed + continue; + } + if ( CheckedNode(nodeNum,ent->s.number) == CHECKED_PASSED ) + {//already checked this node against ent and it passed + } + else + {//haven't checked this node against ent yet + if ( NodeFailed( ent, nodeNum ) ) + { + SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); + continue; + } + //okay, since we only have to do this once, let's check to see if this node is even usable (could help us short-circuit a whole loop of the dest nodes) + radius = node->GetRadius(); + + //If we're not within the known clear radius of this node OR out of Z height range... + if ( (*nci).distance >= (radius*radius) || ( fabs( position[2] - ent->r.currentOrigin[2] ) >= MAX_Z_DELTA ) ) + { + //We're not *within* this node, so check clear path, etc. + + //FIXME: any way to call G_FindClosestPointOnLineSegment and see if I can at least get to the waypoint's path + if ( flags & NF_CLEAR_PATH )//|| flags & NF_CLEAR_LOS ) + {//need a clear path or LOS + if ( !SV_inPVS( ent->r.currentOrigin, position ) ) + {//not even potentially clear + SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); + continue; + } + } + + //Do we need a clear path? + if ( flags & NF_CLEAR_PATH ) + { + if ( TestNodePath( ent, goal->s.number, position, qtrue ) == false ) + { + SetCheckedNode( nodeNum, ent->s.number, CHECKED_FAILED ); + continue; + } + } + }//otherwise, inside the node so it must be clear (?) + SetCheckedNode( nodeNum, ent->s.number, CHECKED_PASSED ); + } + + if ( d_altRoutes->integer ) + { + //calc the paths for this node if they're out of date + nodeFlags = node->GetFlags(); + if ( (nodeFlags&NF_RECALC) ) + { + //Com_Printf( S_COLOR_CYAN"%d recalcing paths from node %d\n", svs.time, nodeNum ); + CalculatePath( node ); + } + } + + STL_ITERATE( nci2, nodeChain2 ) + { + node2 = m_nodes[(*nci2).nodeID]; + nodeNum2 = (*nci2).nodeID; + if ( d_altRoutes->integer ) + { + //calc the paths for this node if they're out of date + nodeFlags = node2->GetFlags(); + if ( (nodeFlags&NF_RECALC) ) + { + //Com_Printf( S_COLOR_CYAN"%d recalcing paths from node %d\n", svs.time, nodeNum2 ); + CalculatePath( node2 ); + } + } + + node2->GetPosition( position2 ); + //Okay, first get the entire path cost, including distance to first node from ents' positions + cost = floor(Distance( ent->r.currentOrigin, position ) + Distance( goal->r.currentOrigin, position2 )); + + if ( d_altRoutes->integer ) + { + nextNode = GetBestNodeAltRoute( (*nci).nodeID, (*nci2).nodeID, &pathCost, bestNode ); + cost += pathCost; + } + else + { + cost += GetPathCost( (*nci).nodeID, (*nci2).nodeID ); + } + + if ( cost >= bestCost ) + { + continue; + } + + //okay, this is the shortest path we've found yet, check clear path, etc. + if ( CheckedNode( nodeNum2, goal->s.number ) == CHECKED_FAILED ) + {//already checked this node against goal and it failed + continue; + } + if ( CheckedNode( nodeNum2, goal->s.number ) == CHECKED_PASSED ) + {//already checked this node against goal and it passed + } + else + {//haven't checked this node against goal yet + if ( NodeFailed( goal, nodeNum2 ) ) + { + SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); + continue; + } + radius = node2->GetRadius(); + + //If we're not within the known clear radius of this node OR out of Z height range... + if ( (*nci2).distance >= (radius*radius) || ( fabs( position2[2] - goal->r.currentOrigin[2] ) >= MAX_Z_DELTA ) ) + { + //We're not *within* this node, so check clear path, etc. + + if ( flags & NF_CLEAR_PATH )//|| flags & NF_CLEAR_LOS ) + {//need a clear path or LOS + if ( !SV_inPVS( goal->r.currentOrigin, position2 ) ) + {//not even potentially clear + SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); + continue; + } + } + //Do we need a clear path? + if ( flags & NF_CLEAR_PATH ) + { + if ( TestNodePath( goal, ent->s.number, position2, qfalse ) == false )//qtrue? + { + SetCheckedNode( nodeNum2, goal->s.number, CHECKED_FAILED ); + continue; + } + } + }//otherwise, inside the node so it must be clear (?) + SetCheckedNode( nodeNum2, goal->s.number, CHECKED_PASSED ); + } + + bestCost = cost; + bestNode = nextNode; + ent->waypoint = (*nci).nodeID; + goal->waypoint = (*nci2).nodeID; + } + } + + if ( !d_altRoutes->integer ) + {//bestNode would not have been set by GetBestNodeAltRoute above, so get it here + if ( ent->waypoint != NODE_NONE && goal->waypoint != NODE_NONE ) + {//have 2 valid waypoints which means a valid path + bestNode = GetBestNodeAltRoute( ent->waypoint, goal->waypoint, &bestCost, NODE_NONE ); + } + } + return bestNode; +} + +/* +------------------------- +GetNearestWaypoint +------------------------- +*/ + +int CNavigator::GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int targetID ) +{ + int bestNode = NODE_NONE; + //Must have nodes + if ( m_nodes.size() == 0 ) + return NODE_NONE; + + if ( targetID == NODE_NONE ) + { + //Try and find an early match using our last node + bestNode = TestBestFirst( ent, lastID, flags ); + + if ( bestNode != NODE_NONE ) + return bestNode; + }//else can't rely on testing last, we want best to targetID + +///////////////////////////////////////////////// + +#define MAX_Z_DELTA 18 + +///////////////////////////////////////////////// + + nodeChain_l nodeChain; + nodeChain_l::iterator nci; + + //Collect all nodes within a certain radius + CollectNearestNodes( ent->r.currentOrigin, NODE_COLLECT_RADIUS, NODE_COLLECT_MAX, nodeChain ); + + vec3_t position; + int radius; + int dist, bestDist = Q3_INFINITE; + CNode *node; + + //Look through all nodes + STL_ITERATE( nci, nodeChain ) + { + node = m_nodes[(*nci).nodeID]; + + node->GetPosition( position ); + + radius = node->GetRadius(); + + if ( NodeFailed( ent, (*nci).nodeID ) ) + { + continue; + } + //Are we within the known clear radius of this node? + if ( (*nci).distance < (radius*radius) ) + { + //Do a z-difference sanity check + if ( fabs( position[2] - ent->r.currentOrigin[2] ) < MAX_Z_DELTA ) + { + //Found one + return (*nci).nodeID; + } + } + + //We're not *within* this node, so... + if ( CheckedNode((*nci).nodeID,ent->s.number) == CHECKED_FAILED ) + { + continue; + } + else if ( CheckedNode((*nci).nodeID,ent->s.number) == CHECKED_FAILED ) + { + continue; + } + else + { + //Do we need a clear path? + if ( flags & NF_CLEAR_PATH ) + { + if ( TestNodePath( ent, ENTITYNUM_NONE, position, qfalse ) == false )//qtrue? + { + SetCheckedNode((*nci).nodeID,ent->s.number,CHECKED_FAILED); + continue; + } + } + + //Do we need a clear line of sight? + /* + if ( flags & NF_CLEAR_LOS ) + { + if ( TestNodeLOS( ent, position ) == false ) + { + nodeChecked[(*nci).nodeID][ent->s.number] = CHECKED_FAILED; + continue; + } + } + */ + SetCheckedNode((*nci).nodeID,ent->s.number,CHECKED_PASSED); + } + + if ( targetID != WAYPOINT_NONE ) + {//we want to find the one with the shortest route here + dist = GetPathCost( (*nci).nodeID, targetID ); + if ( dist < bestDist ) + { + bestDist = dist; + bestNode = (*nci).nodeID; + } + } + else + {//first one we find is fine + bestNode = (*nci).nodeID; + break; + } + } + + //Found one, we're done + return bestNode; +} + +/* +------------------------- +ShowPath +------------------------- +*/ + +void CNavigator::ShowPath( int start, int end ) +{ + //Validate the start position + if ( ( start < 0 ) || ( start >= m_nodes.size() ) ) + return; + + //Validate the end position + if ( ( end < 0 ) || ( end >= m_nodes.size() ) ) + return; + + CNode *startNode = m_nodes[ start ]; + CNode *endNode = m_nodes[ end ]; + + CNode *moveNode = startNode; + CNode *testNode = NULL; + + int bestNode; + vec3_t startPos, endPos; + + int runAway = 0; + + //Draw out our path + while ( moveNode != endNode ) + { + bestNode = GetBestNode( moveNode->GetID(), end ); + + //Some nodes may be fragmented + if ( bestNode == -1 ) + { + Com_Printf("No connection possible between node %d and %d\n", start, end ); + return; + } + + //This is our next node on the path + testNode = m_nodes[ bestNode ]; + + //Get their origins + moveNode->GetPosition( startPos ); + testNode->GetPosition( endPos ); + + //Draw the edge + //rwwFIXMEFIXME: ... + //CG_DrawEdge( startPos, endPos, EDGE_PATH ); + + //Take a new best node + moveNode = testNode; + + if ( runAway++ > 64 ) + { + Com_Printf("Potential Run-away path!\n"); + return; + } + } +} + +static map CheckedNodes; +void CNavigator::ClearCheckedNodes( void ) +{ + CheckedNodes.clear(); +} + +byte CNavigator::CheckedNode(int wayPoint,int ent) +{ + //assert(wayPoint>=0&&wayPoint= MAX_STORED_WAYPOINTS) + { + return CHECKED_NO; + } + assert(ent>=0&&ent::iterator f=CheckedNodes.find(wayPoint*MAX_GENTITIES+ent); + if (f!=CheckedNodes.end()) + { + return (*f).second; + } + return CHECKED_NO; +} + +void CNavigator::SetCheckedNode(int wayPoint,int ent,byte value) +{ + //assert(wayPoint>=0&&wayPoint= MAX_STORED_WAYPOINTS) + { + return; + } + assert(ent>=0&&entfailedWaypointCheckTime && ent->failedWaypointCheckTime < svs.time ) + { + int failed = 0; + //do this only once every 1 second + for ( j = 0; j < MAX_FAILED_NODES; j++ ) + { + if ( ent->failedWaypoints[j] != 0 ) + { + failed++; + //-1 because 0 is a valid node but also the default, so we add one when we add one + m_nodes[ent->failedWaypoints[j]-1]->GetPosition( nodePos ); + if ( !GNavCallback_NAV_ClearPathToPoint( ent, ent->r.mins, ent->r.maxs, nodePos, (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP), ENTITYNUM_NONE ) ) + {//no path clear of architecture, so clear this since we can't check against entities + ent->failedWaypoints[j] = 0; + failed--; + } + //have clear architectural path, now check against ents only + else if ( GNavCallback_NAV_ClearPathToPoint( ent, ent->r.mins, ent->r.maxs, nodePos, CONTENTS_BODY, ENTITYNUM_NONE ) ) + {//clear of ents, too, so all clear, clear this one out + ent->failedWaypoints[j] = 0; + failed--; + } + } + } + if ( !failed ) + { + ent->failedWaypointCheckTime = 0; + } + else + { + ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTERVAL + Q_irand( 0, 1000 ); + } + } +} + +void CNavigator::AddFailedNode( sharedEntity_t *ent, int nodeID ) +{ + int j; + for ( j = 0; j < MAX_FAILED_NODES; j++ ) + { + if ( ent->failedWaypoints[j] == 0 ) + { + ent->failedWaypoints[j] = nodeID+1;//+1 because 0 is the default value and that's a valid node, so we take the +1 out when we check the node above + if ( !ent->failedWaypointCheckTime ) + { + ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTITIAL; + } + return; + } + if ( ent->failedWaypoints[j] == nodeID+1 ) + {//already have this one marked as failed + return; + } + } + if ( j == MAX_FAILED_NODES )//check not needed, but... + {//ran out of failed nodes, get rid of first one, shift rest up + for ( j = 0; j < MAX_FAILED_NODES-1; j++ ) + { + ent->failedWaypoints[j] = ent->failedWaypoints[j+1]; + } + } + ent->failedWaypoints[MAX_FAILED_NODES-1] = nodeID+1; + if ( !ent->failedWaypointCheckTime ) + { + ent->failedWaypointCheckTime = svs.time + CHECK_FAILED_EDGE_INTITIAL; + } +} + +qboolean CNavigator::NodeFailed( sharedEntity_t *ent, int nodeID ) +{ + for ( int j = 0; j < MAX_FAILED_NODES; j++ ) + { + if ( (ent->failedWaypoints[j]-1) == nodeID ) + { + return qtrue; + } + } + return qfalse; +} + +qboolean CNavigator::NodesAreNeighbors( int startID, int endID ) +{//See if these 2 are neighbors + if ( startID == endID ) + { + return qfalse; + } + + CNode *start = m_nodes[startID]; + int nextID = -1; + //NOTE: we only check start because we assume all connections are 2-way + for ( int i = 0; i < start->GetNumEdges(); i++ ) + { + nextID = start->GetEdge(i); + if ( nextID == endID ) + { + return qtrue; + } + } + //not neighbors + return qfalse; +} + +void CNavigator::ClearFailedEdge( failedEdge_t *failedEdge ) +{ + if ( !failedEdge ) + { + return; + } + + //clear the edge failed flags + /* + CNode *node = m_nodes[failedEdge->startID]; + int edgeNum = node->GetEdgeNumToNode( failedEdge->endID ); + int flags; + if ( edgeNum != -1 ) + { + flags = node->GetEdgeFlags( edgeNum )&~EFLAG_FAILED; + node->SetEdgeFlags( edgeNum, flags ); + } + node = m_nodes[failedEdge->endID]; + edgeNum = node->GetEdgeNumToNode( failedEdge->startID ); + if ( edgeNum != -1 ) + { + flags = node->GetEdgeFlags( edgeNum )&~EFLAG_FAILED; + node->SetEdgeFlags( edgeNum, flags ); + } + */ + //clear failedEdge info + SetEdgeCost( failedEdge->startID, failedEdge->endID, -1 ); + failedEdge->startID = failedEdge->endID = WAYPOINT_NONE; + failedEdge->entID = ENTITYNUM_NONE; + failedEdge->checkTime = 0; +} + +void CNavigator::ClearAllFailedEdges( void ) +{ + memset( &failedEdges, WAYPOINT_NONE, sizeof( failedEdges ) ); + for ( int j = 0; j < MAX_FAILED_EDGES; j++ ) + { + ClearFailedEdge( &failedEdges[j] ); + } +} + +int CNavigator::EdgeFailed( int startID, int endID ) +{ + //OPTIMIZED WAY (bjg 01/02) + //find in lookup map + pair findValue; + findValue = m_edgeLookupMap.equal_range(startID); + while ( findValue.first != findValue.second ) + { + if( failedEdges[findValue.first->second].endID == endID) + { + return findValue.first->second; + } + ++findValue.first; + } + findValue = m_edgeLookupMap.equal_range(endID); + while ( findValue.first != findValue.second ) + { + if( failedEdges[findValue.first->second].endID == startID) + { + return findValue.first->second; + } + ++findValue.first; + } + + return -1; + + //Old way (linear search) + /* + for ( int j = 0; j < MAX_FAILED_EDGES; j++ ) + { + if ( failedEdges[j].startID == startID ) + { + if ( failedEdges[j].endID == endID ) + { + return j; + } + } + else if ( failedEdges[j].startID == endID ) + { + if ( failedEdges[j].endID == startID ) + { + return j; + } + } + } + return -1; + */ +} + +void CNavigator::AddFailedEdge( int entID, int startID, int endID ) +{ + int j;//, nextID; + + //Must have nodes + if ( m_nodes.size() == 0 ) + return; + + if ( d_patched->integer ) + {//use patch-style navigation + if ( startID == endID ) + {//not an edge! + return; + } + } + + //Validate the ent number + if ( ( entID < 0 ) || ( entID > ENTITYNUM_NONE ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"NAV ERROR: envalid ent %d\n", entID ); + assert(0&&"invalid entID"); +#endif + return; + } + + //Validate the start position + if ( ( startID < 0 ) || ( startID >= m_nodes.size() ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"NAV ERROR: tried to fail invalid waypoint %d\n", startID ); + assert(0&&"invalid failed edge"); +#endif + return; + } + + //Validate the end position + if ( ( endID < 0 ) || ( endID >= m_nodes.size() ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"NAV ERROR: tried to fail invalid waypoint %d\n", endID ); + assert(0&&"invalid failed edge"); +#endif + return; + } + + //First see if we already have this one + if ( (j = EdgeFailed( startID, endID )) != -1 ) + { + //just remember this guy instead + failedEdges[j].entID = entID; + return; + } + + //Okay, new one, find an empty slot + for ( j = 0; j < MAX_FAILED_EDGES; j++ ) + { + if ( failedEdges[j].startID == WAYPOINT_NONE ) + { + failedEdges[j].startID = startID; + failedEdges[j].endID = endID; + //Check one second from now to see if it's clear + failedEdges[j].checkTime = svs.time + CHECK_FAILED_EDGE_INTERVAL + Q_irand( 0, 1000 ); + + m_edgeLookupMap.insert(pair(startID, j)); + + /* + //DISABLED this for now, makes people stand around too long when + // collision avoidance just wasn't at it's best but path is clear + CNode *start = m_nodes[startID]; + CNode *end = m_nodes[endID]; + + for ( int i = 0; i < start->GetNumEdges(); i++ ) + { + nextID = start->GetEdge(i); + + if ( EdgeFailed( startID, nextID ) != -1 ) + { + //This edge blocked, check next + continue; + } + + if ( nextID == endID || end->GetRank( nextID ) >= 0 ) + {//neighbor of or route to end + //There's an alternate route, so don't check this one for 10 seconds + failedEdges[j].checkTime = svs.time + CHECK_FAILED_EDGE_INTITIAL; + break; + } + } + */ + + //Remember who needed it + failedEdges[j].entID = entID; + + //set the edge failed flags + /* + CNode *node = m_nodes[startID]; + int edgeNum = node->GetEdgeNumToNode( endID ); + int flags; + if ( edgeNum != -1 ) + { + flags = node->GetEdgeFlags( edgeNum )|EFLAG_FAILED; + node->SetEdgeFlags( edgeNum, flags ); + } + node = m_nodes[endID]; + edgeNum = node->GetEdgeNumToNode( startID ); + if ( edgeNum != -1 ) + { + flags = node->GetEdgeFlags( edgeNum )|EFLAG_FAILED; + node->SetEdgeFlags( edgeNum, flags ); + } + */ + + //stuff the index to this one in our lookup map + + //now recalc all the paths! + if ( pathsCalculated ) + { + //reconnect the nodes and mark every node's flag NF_RECALC + //Com_Printf( S_COLOR_CYAN"%d marking all nodes for recalc\n", svs.time ); + SetEdgeCost( startID, endID, Q3_INFINITE ); + FlagAllNodes( NF_RECALC ); + } + return; + } + } + +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"NAV ERROR: too many blocked waypoint connections (%d)!!!\n", j ); +#endif +} + +qboolean CNavigator::CheckFailedEdge( failedEdge_t *failedEdge ) +{ + if ( !failedEdge ) + { + return qfalse; + } + + //Every 1 second, see if our failed edges are clear + if ( failedEdge->checkTime < svs.time ) + { + if ( failedEdge->startID != WAYPOINT_NONE ) + { + vec3_t start, end, mins, maxs; + int ignore, clipmask; + sharedEntity_t *ent = SV_GentityNum(failedEdge->entID); //(failedEdge->entIDentID]:NULL; + int hitEntNum; + + if ( !ent || /*!ent->inuse || !ent->client || ent->health <= 0*/ (ent->s.eType != ET_PLAYER && ent->s.eType != ET_NPC) || + (ent->s.eFlags & EF_DEAD)) + { + VectorSet( mins, -15, -15, DEFAULT_MINS_2+STEPSIZE ); + VectorSet( maxs, 15, 15, DEFAULT_MAXS_2 ); + ignore = ENTITYNUM_NONE; + clipmask = MASK_NPCSOLID; + } + else + { + VectorCopy( ent->r.mins, mins ); + mins[2] += STEPSIZE; + VectorCopy( ent->r.maxs, maxs ); + ignore = failedEdge->entID; + clipmask = MASK_SOLID;//ent->clipmask; //rwwFIXMEFIXME: share clipmask? + } + + if ( maxs[2] < mins[2] ) + {//don't invert bounding box + maxs[2] = mins[2]; + } + + m_nodes[failedEdge->startID]->GetPosition( start ); + m_nodes[failedEdge->endID]->GetPosition( end ); + + //See if it's NAV_ClearPath... +#if 0 + hitEntNum = NAVNEW_ClearPathBetweenPoints( start, end, mins, maxs, ignore, clipmask|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP );//NOTE: should we really always include monsterclip (physically blocks NPCs) and botclip (do not enter)? +#else + trace_t trace; + + //Test if they're even conceivably close to one another + if ( !SV_inPVS( start, end ) ) + { + return qfalse; + } + + SV_Trace( &trace, start, mins, maxs, end, ignore, clipmask|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP, qfalse, 0, 10 );//NOTE: should we really always include monsterclip (physically blocks NPCs) and botclip (do not enter)? + + if( trace.startsolid == qtrue || trace.allsolid == qtrue ) + { + return qfalse; + } + hitEntNum = trace.entityNum; +#endif + //if we did hit something, see if it's just an auto-door and allow it + if ( hitEntNum != ENTITYNUM_NONE && GNavCallback_G_EntIsUnlockedDoor( hitEntNum ) ) + { + hitEntNum = ENTITYNUM_NONE; + } + else if ( hitEntNum == failedEdge->entID ) + {//don't hit the person who initially marked the edge failed + hitEntNum = ENTITYNUM_NONE; + } + if ( hitEntNum == ENTITYNUM_NONE ) + { + //If so, clear it + ClearFailedEdge( failedEdge ); + return qtrue; + } + else + { + //Check again in one second + failedEdge->checkTime = svs.time + CHECK_FAILED_EDGE_INTERVAL + Q_irand( 0, 1000 ); + } + } + } + return qfalse; +} + +void CNavigator::CheckAllFailedEdges( void ) +{ + failedEdge_t *failedEdge; + qboolean clearedAny = qfalse; + + //Must have nodes + if ( m_nodes.size() == 0 ) + return; + + for ( int j = 0; j < MAX_FAILED_EDGES; j++ ) + { + failedEdge = &failedEdges[j]; + + clearedAny = CheckFailedEdge( failedEdge )?qtrue:clearedAny; + } + if ( clearedAny ) + {//need to recalc the paths + if ( pathsCalculated ) + { + //reconnect the nodes and mark every node's flag NF_RECALC + //Com_Printf( S_COLOR_CYAN"%d marking all nodes for recalc\n", svs.time ); + FlagAllNodes( NF_RECALC ); + } + } +} + +qboolean CNavigator::RouteBlocked( int startID, int testEdgeID, int endID, int rejectRank ) +{ + int nextID, edgeID, lastID, bestNextID = NODE_NONE; + int bestRank = rejectRank; + int testRank; + qboolean allEdgesFailed; + CNode *end; + CNode *next; + + + if ( EdgeFailed( startID, testEdgeID ) != -1 ) + { + return qtrue; + } + + if ( testEdgeID == endID ) + {//Neighbors, checked out, all clear + return qfalse; + } + + //Okay, first edge is clear, now check rest of route! + end = m_nodes[ endID ]; + nextID = testEdgeID; + lastID = startID; + + while( 1 ) + { + next = m_nodes[ nextID ]; + allEdgesFailed = qtrue; + + for ( int i = 0; i < next->GetNumEdges(); i++ ) + { + edgeID = next->GetEdge(i); + + if ( edgeID == lastID ) + {//Don't backtrack + continue; + } + + if ( edgeID == startID ) + {//Don't loop around + continue; + } + + if ( EdgeFailed( nextID, edgeID ) != -1 ) + { + //This edge blocked, check next + continue; + } + + if ( edgeID == endID ) + {//We got there all clear! + return qfalse; + } + + //Still going... + testRank = end->GetRank( edgeID ); + + if ( testRank < 0 ) + {//No route this way + continue; + } + + //Is the rank good enough? + if ( testRank < bestRank ) + { + bestNextID = edgeID; + bestRank = testRank; + allEdgesFailed = qfalse; + } + } + + if ( allEdgesFailed ) + { + //This route has no clear way of getting to end + return qtrue; + } + else + { + lastID = nextID; + nextID = bestNextID; + } + } +} + +/* +------------------------- +GetBestNodeAltRoute +------------------------- +*/ + +int CNavigator::GetBestNodeAltRoute( int startID, int endID, int *pathCost, int rejectID ) +{ + //Must have nodes + if ( m_nodes.size() == 0 ) + return WAYPOINT_NONE; + + //Validate the start position + if ( ( startID < 0 ) || ( startID >= m_nodes.size() ) ) + return WAYPOINT_NONE; + + //Validate the end position + if ( ( endID < 0 ) || ( endID >= m_nodes.size() ) ) + return WAYPOINT_NONE; + + //Is it the same node? + if ( startID == endID ) + { + if ( !d_altRoutes->integer || EdgeFailed( startID, endID ) == -1 ) + { + return startID; + } + else + { + return WAYPOINT_NONE; + } + } + + CNode *start = m_nodes[ startID ]; + + int bestNode = -1; + int bestRank = Q3_INFINITE; + int testRank, rejectRank = Q3_INFINITE; + int bestCost = Q3_INFINITE; + + *pathCost = 0; + + //Find the minimum rank of the edge(s) we want to reject as paths + if ( rejectID != WAYPOINT_NONE ) + { + for ( int i = 0; i < start->GetNumEdges(); i++ ) + { + if ( start->GetEdge(i) == rejectID ) + { + rejectRank = GetPathCost( startID, endID );//end->GetRank( start->GetEdge(i) ); + break; + } + } + } + + for ( int i = 0; i < start->GetNumEdges(); i++ ) + { + int edgeID = start->GetEdge(i); + + testRank = GetPathCost( edgeID, endID );//end->GetRank( edgeID ); + + //Make sure it's not worse than our reject rank + if ( testRank >= rejectRank ) + continue; + + //Found one + if ( edgeID == endID ) + { + if ( !d_altRoutes->integer || !RouteBlocked( startID, edgeID, endID, rejectRank ) ) + { + *pathCost += start->GetEdgeCost( i ); + return edgeID; + } + else + {//this is blocked, can't consider it + continue; + } + } + + //No possible connection + if ( testRank == NODE_NONE ) + { + *pathCost = Q3_INFINITE; + return NODE_NONE; + } + + //Found a better one + if ( testRank < bestRank ) + { + //FIXME: make sure all the edges down from startID through edgeID to endID + // does NOT include a failedEdge... + if ( !d_altRoutes->integer || !RouteBlocked( startID, edgeID, endID, rejectRank ) ) + { + bestNode = edgeID; + bestRank = testRank; + bestCost = start->GetEdgeCost(i)+testRank; + } + } + } + + *pathCost = bestCost; + + return bestNode; +} +/* +------------------------- +GetBestNodeAltRoute +overloaded so you don't have to pass a pathCost int pointer in +------------------------- +*/ + +int CNavigator::GetBestNodeAltRoute( int startID, int endID, int rejectID ) +{ + int junk; + return GetBestNodeAltRoute( startID, endID, &junk, rejectID ); +} +/* +------------------------- +GetBestNode +------------------------- +*/ + +int CNavigator::GetBestNode( int startID, int endID, int rejectID ) +{ + //Validate the start position + if ( ( startID < 0 ) || ( startID >= m_nodes.size() ) ) + return WAYPOINT_NONE; + + //Validate the end position + if ( ( endID < 0 ) || ( endID >= m_nodes.size() ) ) + return WAYPOINT_NONE; + + if ( startID == endID ) + return startID; + + CNode *start = m_nodes[ startID ]; + CNode *end = m_nodes[ endID ]; + + int bestNode = -1; + int bestRank = Q3_INFINITE; + int testRank, rejectRank = 0; + + if ( rejectID != WAYPOINT_NONE ) + { + for ( int i = 0; i < start->GetNumEdges(); i++ ) + { + if ( start->GetEdge(i) == rejectID ) + { + rejectRank = end->GetRank( start->GetEdge(i) ); + break; + } + } + } + + for ( int i = 0; i < start->GetNumEdges(); i++ ) + { + int edgeID = start->GetEdge(i); + + //Found one + if ( edgeID == endID ) + return edgeID; + + testRank = end->GetRank( edgeID ); + + //Found one + if ( testRank <= rejectRank ) + continue; + + //No possible connection + if ( testRank == NODE_NONE ) + return NODE_NONE; + + //Found a better one + if ( testRank < bestRank ) + { + bestNode = edgeID; + bestRank = testRank; + } + } + + return bestNode; +} + +/* +------------------------- +GetNodePosition +------------------------- +*/ + +int CNavigator::GetNodePosition( int nodeID, vec3_t out ) +{ + //Validate the number + if ( ( nodeID < 0 ) || ( nodeID >= m_nodes.size() ) ) + return false; + + CNode *node = m_nodes[ nodeID ]; + + node->GetPosition( out ); + + return true; +} + +/* +------------------------- +GetNodeNumEdges +------------------------- +*/ + +int CNavigator::GetNodeNumEdges( int nodeID ) +{ + if ( ( nodeID < 0 ) || ( nodeID >= m_nodes.size() ) ) + return -1; + + CNode *node = m_nodes[ nodeID ]; + + assert( node ); + + return node->GetNumEdges(); +} + +/* +------------------------- +GetNodeEdge +------------------------- +*/ + +int CNavigator::GetNodeEdge( int nodeID, int edge ) +{ + if ( ( nodeID < 0 ) || ( nodeID >= m_nodes.size() ) ) + return -1; + + CNode *node = m_nodes[ nodeID ]; + + assert( node ); + + return node->GetEdge( edge ); +} + +/* +------------------------- +Connected +------------------------- +*/ + +bool CNavigator::Connected( int startID, int endID ) +{ + //Validate the start position + if ( ( startID < 0 ) || ( startID >= m_nodes.size() ) ) + return false; + + //Validate the end position + if ( ( endID < 0 ) || ( endID >= m_nodes.size() ) ) + return false; + + if ( startID == endID ) + return true; + + CNode *start = m_nodes[ startID ]; + CNode *end = m_nodes[ endID ]; + + for ( int i = 0; i < start->GetNumEdges(); i++ ) + { + int edgeID = start->GetEdge(i); + + //Found one + if ( edgeID == endID ) + return true; + + if ( ( end->GetRank( edgeID ) ) != NODE_NONE ) + return true; + } + + return false; +} + +/* +------------------------- +GetPathCost +------------------------- +*/ + +unsigned int CNavigator::GetPathCost( int startID, int endID ) +{ + //Validate the start position + if ( ( startID < 0 ) || ( startID >= m_nodes.size() ) ) + return Q3_INFINITE; // return 0; + + //Validate the end position + if ( ( endID < 0 ) || ( endID >= m_nodes.size() ) ) + return Q3_INFINITE; // return 0; + + CNode *startNode = m_nodes[ startID ]; + + if ( !startNode->GetNumEdges() ) + {//WTF? Solitary waypoint! Bad designer! + return Q3_INFINITE; // return 0; + } + + CNode *endNode = m_nodes[ endID ]; + + CNode *moveNode = startNode; + + int bestNode; + int pathCost = 0; + int bestCost; + + int bestRank; + int testRank; + + int dontScrewUp = 0; + + //Draw out our path + while ( moveNode != endNode ) + { + bestRank = WORLD_SIZE; + bestNode = -1; + bestCost = 0; + + for ( int i = 0; i < moveNode->GetNumEdges(); i++ ) + { + int edgeID = moveNode->GetEdge(i); + + //Done + if ( edgeID == endID ) + { + return pathCost + moveNode->GetEdgeCost( i ); + } + + testRank = endNode->GetRank( edgeID ); + + //No possible connection + if ( testRank == NODE_NONE ) + { + return Q3_INFINITE; // return 0; + } + + //Found a better one + if ( testRank < bestRank ) + { + bestNode = edgeID; + bestRank = testRank; + bestCost = moveNode->GetEdgeCost( i ); + } + } + + pathCost += bestCost; + + //Take a new best node + moveNode = m_nodes[ bestNode ]; + dontScrewUp++; + + if (dontScrewUp > 40000) + { //ok, I think something probably screwed up. + break; + } + } + + return pathCost; +} + +/* +------------------------- +GetEdgeCost +------------------------- +*/ + +unsigned int CNavigator::GetEdgeCost( int startID, int endID ) +{ + //Validate the start position + if ( ( startID < 0 ) || ( startID >= m_nodes.size() ) ) + return Q3_INFINITE; // return 0; + + //Validate the end position + if ( ( endID < 0 ) || ( endID >= m_nodes.size() ) ) + return Q3_INFINITE; // return 0; + + CNode *start = m_nodes[startID]; + CNode *end = m_nodes[endID]; + + return GetEdgeCost( start, end ); +} + +/* +------------------------- +GetProjectedNode +------------------------- +*/ + +int CNavigator::GetProjectedNode( vec3_t origin, int nodeID ) +{ + //Validate the start position + if ( ( nodeID < 0 ) || ( nodeID >= m_nodes.size() ) ) + return NODE_NONE; + + CNode *node = m_nodes[nodeID]; + CNode *tempNode; + + float bestDot = 0.0f; + int bestNode = NODE_NONE; + + vec3_t targetDir, basePos, tempDir, tempPos; + float dot; + + //Setup our target direction + node->GetPosition( basePos ); + + VectorSubtract( origin, basePos, targetDir ); + VectorNormalize( targetDir ); + + //Go through all the edges + for ( int i = 0; i < node->GetNumEdges(); i++ ) + { + tempNode = m_nodes[node->GetEdge(i)]; + tempNode->GetPosition( tempPos ); + + VectorSubtract( tempPos, basePos, tempDir ); + VectorNormalize( tempDir ); //FIXME: Retain the length here if you want it + + dot = DotProduct( targetDir, tempDir ); + + if ( dot < 0.0f ) + continue; + + if ( dot > bestDot ) + { + bestDot = dot; + bestNode = tempNode->GetID(); + } + } + + return bestNode; +} + +// This is the PriorityQueue stuff for lists of connections +// better than linear (1/21/02 BJG) +////////////////////////////////////////////////////////////////// +// Helper pop_mHeap algorithm class +////////////////////////////////////////////////////////////////// +class NodeTotalGreater +{ +public: + bool operator()( CEdge * first, CEdge * second ) const { + return( first->m_cost > second->m_cost ); + } +}; + + +////////////////////////////////////////////////////////////////// +// Destructor - Deallocate any remaining pointers in the queue +////////////////////////////////////////////////////////////////// +CPriorityQueue::~CPriorityQueue() +{ + while (!Empty()) + { + delete Pop(); + } +} + +////////////////////////////////////////////////////////////////// +// Standard Iterative Search +////////////////////////////////////////////////////////////////// +CEdge* CPriorityQueue::Find(int npNum) +{ + for(vector::iterator HeapIter=mHeap.begin(); HeapIter!=mHeap.end(); HeapIter++) + { + if ((*HeapIter)->m_first == npNum) + { + return *HeapIter; + } + } + return 0; +} + +////////////////////////////////////////////////////////////////// +// Remove Node And Resort +////////////////////////////////////////////////////////////////// +CEdge* CPriorityQueue::Pop() +{ + CEdge *edge = mHeap.front(); + + //pop_mHeap will move the node at the front to the position N + //and then sort the mHeap to make positions 1 through N-1 correct + //(STL makes no assumptions about your data and doesn't want to change + //the size of the container.) + std::pop_heap(mHeap.begin(), mHeap.end(), NodeTotalGreater() ); + + //pop_back() will actually remove the last element from the mHeap + //now the mHeap is sorted for positions 1 through N + mHeap.pop_back(); + + return( edge ); +} + +////////////////////////////////////////////////////////////////// +// Add New Node And Resort +////////////////////////////////////////////////////////////////// +void CPriorityQueue::Push(CEdge* theEdge ) +{ + //Pushes the node onto the back of the mHeap + mHeap.push_back( theEdge ); + + //Sorts the new element into the mHeap + std::push_heap( mHeap.begin(), mHeap.end(), NodeTotalGreater() ); +} + +////////////////////////////////////////////////////////////////// +// Find The Node In Question And Resort mHeap Around It +////////////////////////////////////////////////////////////////// +void CPriorityQueue::Update( CEdge* edge ) +{ + for(vector::iterator i=mHeap.begin(); i!=mHeap.end(); i++) + { + if( (*i)->m_first == edge->m_first ) + { //Found node - resort from this position in the mHeap + //(its total value was changed before this function was called) + std::push_heap( mHeap.begin(), i+1, NodeTotalGreater() ); + return; + } + } +} + +////////////////////////////////////////////////////////////////// +// Just a wrapper for stl empty function. +////////////////////////////////////////////////////////////////// +bool CPriorityQueue::Empty() +{ + return( mHeap.empty() ); +}; + diff --git a/codemp/server/NPCNav/navigator.h b/codemp/server/NPCNav/navigator.h new file mode 100644 index 0000000..e75f075 --- /dev/null +++ b/codemp/server/NPCNav/navigator.h @@ -0,0 +1,280 @@ +#ifndef __G_NAVIGATOR__ +#define __G_NAVIGATOR__ + +#define __NEWCOLLECT 1 + +#define _HARD_CONNECT 1 + +//Node flags +#define NF_ANY 0 +//#define NF_CLEAR_LOS 0x00000001 +#define NF_CLEAR_PATH 0x00000002 +#define NF_RECALC 0x00000004 + +//Edge flags +#define EFLAG_NONE 0 +#define EFLAG_BLOCKED 0x00000001 +#define EFLAG_FAILED 0x00000002 + +//Miscellaneous defines +#define NODE_NONE -1 +#define NAV_HEADER_ID 'JNV5' +#define NODE_HEADER_ID 'NODE' + +#pragma warning( disable : 4786) + +#if defined(_WIN32) && !defined(_XBOX) +#define COM_NO_WINDOWS_H +#include +#endif + +#include +#include +#include +using namespace std; + +#include "../server.h" +#include "../../game/q_shared.h" + + +typedef multimap EdgeMultimap; +typedef multimap::iterator EdgeMultimapIt; + + +/* +------------------------- +CEdge +------------------------- +*/ + +class CEdge +{ + +public: + + CEdge( void ); + CEdge( int first, int second, int cost ); + ~CEdge( void ); + + int m_first; + int m_second; + int m_cost; +}; + +/* +------------------------- +CNode +------------------------- +*/ + +class CNode +{ + typedef struct edge_s + { + int ID; + int cost; + BYTE flags; + } edge_t; + + typedef vector< edge_t > edge_v; + +public: + + CNode( void ); + ~CNode( void ); + + static CNode *Create( vec3_t position, int flags, int radius, int ID ); + static CNode *Create( void ); + + void AddEdge( int ID, int cost, int flags = EFLAG_NONE ); + void AddRank( int ID, int rank ); + + void Draw( qboolean radius ); + + int GetID( void ) const { return m_ID; } + void GetPosition( vec3_t position ) const { if ( position ) VectorCopy( m_position, position ); } + + int GetNumEdges( void ) const { return m_numEdges; } + int GetEdgeNumToNode( int ID ); + int GetEdge( int edgeNum ); + int GetEdgeCost( int edgeNum ); + BYTE GetEdgeFlags( int edgeNum ); + void SetEdgeFlags( int edgeNum, int newFlags ); + int GetRadius( void ) const { return m_radius; } + + void InitRanks( int size ); + int GetRank( int ID ); + + int GetFlags( void ) const { return m_flags; } + void AddFlag( int newFlag ) { m_flags |= newFlag; } + void RemoveFlag( int oldFlag ) { m_flags &= ~oldFlag; } + + int Save( int numNodes, fileHandle_t file ); + int Load( int numNodes, fileHandle_t file ); + +protected: + + vec3_t m_position; + int m_flags; + int m_radius; + int m_ID; + + edge_v m_edges; + + int *m_ranks; + int m_numEdges; +}; + +/* +------------------------- +CNavigator +------------------------- +*/ +#define MAX_FAILED_EDGES 32 +class CNavigator +{ + typedef vector < CNode * > node_v; + typedef list < CEdge > edge_l; + +#if __NEWCOLLECT + + typedef struct nodeList_t + { + int nodeID; + unsigned int distance; + }; + + typedef list < nodeList_t > nodeChain_l; + +#endif //__NEWCOLLECT + +public: + + CNavigator( void ); + ~CNavigator( void ); + + void Init( void ); + void Free( void ); + + bool Load( const char *filename, int checksum ); + bool Save( const char *filename, int checksum ); + + int AddRawPoint( vec3_t point, int flags, int radius ); + void CalculatePaths( qboolean recalc=qfalse ); + +#if _HARD_CONNECT + + void HardConnect( int first, int second ); + +#endif + + void ShowNodes( void ); + void ShowEdges( void ); + void ShowPath( int start, int end ); + + int GetNearestNode( sharedEntity_t *ent, int lastID, int flags, int targetID ); + + int GetBestNode( int startID, int endID, int rejectID = NODE_NONE ); + + int GetNodePosition( int nodeID, vec3_t out ); + int GetNodeNumEdges( int nodeID ); + int GetNodeEdge( int nodeID, int edge ); + float GetNodeLeadDistance( int nodeID ); + + int GetNumNodes( void ) const { return m_nodes.size(); } + + bool Connected( int startID, int endID ); + + unsigned int GetPathCost( int startID, int endID ); + unsigned int GetEdgeCost( int startID, int endID ); + + int GetProjectedNode( vec3_t origin, int nodeID ); +//MCG Added BEGIN + void CheckFailedNodes( sharedEntity_t *ent ); + void AddFailedNode( sharedEntity_t *ent, int nodeID ); + qboolean NodeFailed( sharedEntity_t *ent, int nodeID ); + qboolean NodesAreNeighbors( int startID, int endID ); + void ClearFailedEdge( failedEdge_t *failedEdge ); + void ClearAllFailedEdges( void ); + int EdgeFailed( int startID, int endID ); + void AddFailedEdge( int entID, int startID, int endID ); + qboolean CheckFailedEdge( failedEdge_t *failedEdge ); + void CheckAllFailedEdges( void ); + qboolean RouteBlocked( int startID, int testEdgeID, int endID, int rejectRank ); + int GetBestNodeAltRoute( int startID, int endID, int *pathCost, int rejectID = NODE_NONE ); + int GetBestNodeAltRoute( int startID, int endID, int rejectID = NODE_NONE ); + int GetBestPathBetweenEnts( sharedEntity_t *ent, sharedEntity_t *goal, int flags ); + int GetNodeRadius( int nodeID ); + void CheckBlockedEdges( void ); + void ClearCheckedNodes( void ); + byte CheckedNode(int wayPoint,int ent); + void SetCheckedNode(int wayPoint,int ent,byte value); + + void FlagAllNodes( int newFlag ); + + qboolean pathsCalculated; +//MCG Added END + +protected: + + int TestNodePath( sharedEntity_t *ent, int okToHitEntNum, vec3_t position, qboolean includeEnts ); + int TestNodeLOS( sharedEntity_t *ent, vec3_t position ); + int TestBestFirst( sharedEntity_t *ent, int lastID, int flags ); + +#if __NEWCOLLECT + int CollectNearestNodes( vec3_t origin, int radius, int maxCollect, nodeChain_l &nodeChain ); +#else + int CollectNearestNodes( vec3_t origin, int radius, int maxCollect, int *nodeChain ); +#endif //__NEWCOLLECT + + char GetChar( fileHandle_t file ); + int GetInt( fileHandle_t file ); + float GetFloat( fileHandle_t file ); + long GetLong( fileHandle_t file ); + + //void ConnectNodes( void ); + void SetEdgeCost( int ID1, int ID2, int cost ); + int GetEdgeCost( CNode *first, CNode *second ); + void AddNodeEdges( CNode *node, int addDist, edge_l &edgeList, bool *checkedNodes ); + + void CalculatePath( CNode *node ); + + //rww - made failedEdges private as it doesn't seem to need to be public. + //And I'd rather shoot myself than have to devise a way of setting/accessing this + //array via trap calls. + failedEdge_t failedEdges[MAX_FAILED_EDGES]; + + node_v m_nodes; + EdgeMultimap m_edgeLookupMap; +}; + +////////////////////////////////////////////////////////////////////// +// class Priority Queue +////////////////////////////////////////////////////////////////////// +class CPriorityQueue +{ +// CONSTRUCTION /DESTRUCTION +//-------------------------------------------------------------- +public: + CPriorityQueue() {}; + ~CPriorityQueue(); + +// Functionality +//-------------------------------------------------------------- +public: + CEdge* Pop(); + CEdge* Find(int npNum); + void Push( CEdge* theEdge ); + void Update(CEdge* edge ); + bool Empty(); + + +// DATA +//-------------------------------------------------------------- +private: + vector mHeap; +}; + +extern CNavigator navigator; + +#endif //__G_NAVIGATOR__ \ No newline at end of file diff --git a/codemp/server/NPCNav/vssver.scc b/codemp/server/NPCNav/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..1e3f995f4bbf2a60ac7e733a49e09add476014e6 GIT binary patch literal 80 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiWQSp}|JAiw|y3xRZeF{|@oht2WKK#@g2eyXDX aZF7a7cy1tnF_6#wF};BOdU`x7kPiUu#ui8b literal 0 HcmV?d00001 diff --git a/codemp/server/exe_headers.h b/codemp/server/exe_headers.h new file mode 100644 index 0000000..03475b9 --- /dev/null +++ b/codemp/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/codemp/server/server.h b/codemp/server/server.h new file mode 100644 index 0000000..86341ef --- /dev/null +++ b/codemp/server/server.h @@ -0,0 +1,437 @@ +#if defined (_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +#if !defined(SERVER_H_INC) +#define SERVER_H_INC + + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../game/g_public.h" +#include "../game/bg_public.h" + +#ifdef _XBOX +#include "../xbox/XBLive.h" +#include "../xbox/XBoxCommon.h" +#include "../xbox/XBVoice.h" +#endif + +//============================================================================= + +#define PERS_SCORE 0 // !!! MUST NOT CHANGE, SERVER AND + // GAME BOTH REFERENCE !!! + +#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; + qboolean restarting; // if true, send configstring changes during SS_LOADING + int serverId; // changes each server start + int restartedServerId; // serverId before a map_restart + int checksumFeed; // +#ifdef _XBOX + char snapshotCounter; // incremented for each snapshot built +#else + int snapshotCounter; // incremented for each snapshot built +#endif + int timeResidual; // <= 1000 / sv_frame->value + int nextFrameTime; // when time > nextFrameTime, process world + struct cmodel_s *models[MAX_MODELS]; + char *configstrings[MAX_CONFIGSTRINGS]; + svEntity_t svEntities[MAX_GENTITIES]; + + char *entityParsePoint; // used during game VM init + + // the game virtual machine will update these on init and changes + sharedEntity_t *gentities; + int gentitySize; + int num_entities; // current number, <= MAX_GENTITIES + + playerState_t *gameClients; + int gameClientSize; // will be > sizeof(playerState_t) due to game private data + + int restartTime; + + //rwwRMG - added: + int mLocalSubBSPIndex; + int mLocalSubBSPModelOffset; + char *mLocalSubBSPEntityParsePoint; + + char *mSharedMemory; +} server_t; + + + + + +typedef struct { + int areabytes; + byte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + playerState_t ps; + playerState_t vps; //vehicle I'm riding's playerstate (if applicable) -rww +#ifdef _ONEBIT_COMBO + int *pDeltaOneBit; + int *pDeltaOneBitVeh; + int *pDeltaNumBit; + int *pDeltaNumBitVeh; +#endif + 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 + + qboolean sentGamedir; //see if he has been sent an svc_setgame + + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + int reliableSequence; // last added reliable message, not necesarily sent or acknowledged yet + int reliableAcknowledge; // last acknowledged reliable message + int reliableSent; // last sent reliable message, not necesarily acknowledged yet + int messageAcknowledge; + + int gamestateMessageNum; // netchan->outgoingSequence of gamestate + int challenge; + + usercmd_t lastUsercmd; + int lastMessageNum; // for delta compression + int lastClientCommand; // reliable client message sequence + char lastClientCommandString[MAX_STRING_CHARS]; + sharedEntity_t *gentity; // SV_GentityNum(clientnum) + char name[MAX_NAME_LENGTH]; // extracted from userinfo, high bits masked + + // downloading +#ifndef _XBOX // No downloads on Xbox + char downloadName[MAX_QPATH]; // if not empty string, we are downloading + fileHandle_t download; // file being downloaded + int downloadSize; // total bytes (can't use EOF because of paks) + int downloadCount; // bytes sent + int downloadClientBlock; // last block we sent to the client, awaiting ack + int downloadCurrentBlock; // current block number + int downloadXmitBlock; // last block we xmited + unsigned char *downloadBlocks[MAX_DOWNLOAD_WINDOW]; // the buffers for the download blocks + int downloadBlockSize[MAX_DOWNLOAD_WINDOW]; + qboolean downloadEOF; // We have sent the EOF block + int downloadSendTime; // time we last got an ack from the client +#endif + + int deltaMessage; // frame last client usercmd message + int nextReliableTime; // svs.time when another reliable command will be allowed + int lastPacketTime; // svs.time when packet was last received + int lastConnectTime; // svs.time when connection started + int nextSnapshotTime; // send another snapshot when svs.time >= nextSnapshotTime + qboolean rateDelayed; // true if nextSnapshotTime was set based on rate instead of snapshotMsec + 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 + int pureAuthentic; + netchan_t netchan; + + int lastUserInfoChange; //if > svs.time && count > x, deny change -rww + int lastUserInfoCount; //allow a certain number of changes within a certain time period -rww + +#ifdef _XBOX + int refIndex; // Copy of refIndex in xbOnlineInfo.xbPlayerList[] + qboolean usePrivateSlot; // Was this person eligble for a private slot when they joined? +#endif +} client_t; + +//============================================================================= + + +// MAX_CHALLENGES is made large to prevent a denial +// of service attack that could cycle all of them +// out before legitimate users connected +#define MAX_CHALLENGES 1024 + +#define AUTHORIZE_TIMEOUT 5000 + +typedef struct { + netadr_t adr; + int challenge; + int time; // time the last packet was sent to the autherize server + int pingTime; // time the challenge response was sent to client + int firstTime; // time the adr was first used, for authorize timeout checks + qboolean connected; +} challenge_t; + + +#define MAX_MASTERS 8 // max recipients for heartbeat packets + + +// this structure will be cleared only when the game dll changes +typedef struct { + qboolean initialized; // sv_init has completed + + int time; // will be strictly increasing across level changes + + int snapFlagServerBit; // ^= SNAPFLAG_SERVERCOUNT every SV_SpawnServer() + + 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; + challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting + netadr_t redirectAddress; // for rcon return messages + + netadr_t authorizeAddress; // for rcon return messages + +#ifdef _XBOX + int clientRefNum; // Index into xbonlineinfo array +#endif +} serverStatic_t; + +//============================================================================= + +extern serverStatic_t svs; // persistant server info across maps +extern server_t sv; // cleared each map +extern vm_t *gvm; // game virtual machine + +#define MAX_MASTER_SERVERS 5 + +extern cvar_t *sv_fps; +extern cvar_t *sv_timeout; +extern cvar_t *sv_zombietime; +extern cvar_t *sv_rconPassword; +extern cvar_t *sv_privatePassword; +extern cvar_t *sv_allowDownload; +extern cvar_t *sv_maxclients; +extern cvar_t *sv_privateClients; +extern cvar_t *sv_hostname; +extern cvar_t *sv_master[MAX_MASTER_SERVERS]; +extern cvar_t *sv_reconnectlimit; +extern cvar_t *sv_showghoultraces; +extern cvar_t *sv_showloss; +extern cvar_t *sv_padPackets; +extern cvar_t *sv_killserver; +extern cvar_t *sv_mapname; +extern cvar_t *sv_mapChecksum; +extern cvar_t *sv_serverid; +extern cvar_t *sv_maxRate; +extern cvar_t *sv_minPing; +extern cvar_t *sv_maxPing; +extern cvar_t *sv_gametype; +extern cvar_t *sv_pure; +extern cvar_t *sv_floodProtect; +extern cvar_t *sv_allowAnonymous; +extern cvar_t *sv_needpass; + + +//=========================================================== + +// +// 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); + + +void SV_MasterHeartbeat (void); +void SV_MasterShutdown (void); + + + + +// +// sv_init.c +// +void SV_SetConfigstring( int index, const char *val ); +void SV_GetConfigstring( int index, char *buffer, int bufferSize ); +int SV_AddConfigstring (const char *name, int start, int max); + +void SV_SetUserinfo( int index, const char *val ); +void SV_GetUserinfo( int index, char *buffer, int bufferSize ); + +void SV_ChangeMaxClients( void ); +void SV_SpawnServer( char *server, qboolean killBots, ForceReload_e eForceReload ); + + + +// +// sv_client.c +// +void SV_GetChallenge( netadr_t from ); + +void SV_DirectConnect( netadr_t from ); + +void SV_AuthorizeIpPacket( netadr_t from ); + +void SV_SendClientMapChange( client_t *client ); +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ); +void SV_UserinfoChanged( client_t *cl ); + +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ); +void SV_DropClient( client_t *drop, const char *reason ); + +void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ); +void SV_ClientThink (client_t *cl, usercmd_t *cmd); + +void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ); + +// Need to broadcast info about clients on join/leave +#ifdef _XBOX +struct XBPlayerInfo; +void SV_SendClientNewPeer(client_t* client, XBPlayerInfo* info); +void SV_SendClientRemovePeer(client_t* client, int index); +void SV_SendClientXbInfo(client_t *client); +#endif + +// +// sv_ccmds.c +// +void SV_Heartbeat_f( void ); + +// +// sv_snapshot.c +// +void SV_AddServerCommand( client_t *client, const char *cmd ); +void SV_UpdateServerCommandsToClient( client_t *client, msg_t *msg ); +void SV_WriteFrameToClient (client_t *client, msg_t *msg); +void SV_SendMessageToClient( msg_t *msg, client_t *client ); +void SV_SendClientMessages( void ); +void SV_SendClientSnapshot( client_t *client ); + +// +// sv_game.c +// +int SV_NumForGentity( sharedEntity_t *ent ); +sharedEntity_t *SV_GentityNum( int num ); +playerState_t *SV_GameClientNum( int num ); +svEntity_t *SV_SvEntityForGentity( sharedEntity_t *gEnt ); +sharedEntity_t *SV_GEntityForSvEntity( svEntity_t *svEnt ); +void SV_InitGameProgs ( void ); +void SV_ShutdownGameProgs ( void ); +void SV_RestartGameProgs( void ); +qboolean SV_inPVS (const vec3_t p1, const vec3_t p2); + +// +// sv_bot.c +// +void SV_BotFrame( int time ); +int SV_BotAllocateClient(void); +void SV_BotFreeClient( int clientNum ); + +void SV_BotInitCvars(void); +int SV_BotLibSetup( void ); +int SV_BotLibShutdown( void ); +int SV_BotGetSnapshotEntity( int client, int ent ); +int SV_BotGetConsoleMessage( int client, char *buf, int size ); + +void *Bot_GetMemoryGame(int size); +void Bot_FreeMemoryGame(void *ptr); + +int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points); +void BotImport_DebugPolygonDelete(int id); + +//============================================================ +// +// 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( sharedEntity_t *ent ); +// call before removing an entity, and before trying to move one, +// so it doesn't clip against itself + +void SV_LinkEntity( sharedEntity_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 sharedEntity_t *ent ); + + +void SV_SectorList_f( void ); + + +int SV_AreaEntities( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount ); +// fills in a table of entity numbers 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. + + +void SV_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 capsule, int traceFlags, int useLod ); +// 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) + + +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ); +// clip to a specific entity + +// +// sv_net_chan.c +// +void SV_Netchan_Transmit( client_t *client, msg_t *msg); //int length, const byte *data ); +void SV_Netchan_TransmitNextFragment( netchan_t *chan ); +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ); + +#endif // SERVER_H_INC diff --git a/codemp/server/sv_bot.cpp b/codemp/server/sv_bot.cpp new file mode 100644 index 0000000..6de6e72 --- /dev/null +++ b/codemp/server/sv_bot.cpp @@ -0,0 +1,797 @@ +// sv_bot.c +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "server.h" +#include "../game/botlib.h" + +typedef struct bot_debugpoly_s +{ + int inuse; + int color; + int numPoints; + vec3_t points[128]; +} bot_debugpoly_t; + +static bot_debugpoly_t *debugpolygons; +int bot_maxdebugpolys; + +extern botlib_export_t *botlib_export; +int bot_enable; + +static int gWPNum = 0; +static wpobject_t *gWPArray[MAX_WPARRAY_SIZE]; + +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; +} + +int SV_OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore, int rmg) +{ + trace_t tr; + + if (rmg) + { + SV_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID, 0, 0, 10); + } + else + { + SV_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID, 0, 0, 10); + } + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + + return 0; +} + +void *BotVMShift( int ptr ); + +void SV_BotWaypointReception(int wpnum, wpobject_t **wps) +{ + int i = 0; + + gWPNum = wpnum; + + while (i < gWPNum) + { + gWPArray[i] = (wpobject_t *)BotVMShift((int)wps[i]); + i++; + } +} + +/* +================== +SV_BotCalculatePaths +================== +*/ +void SV_BotCalculatePaths(int rmg) +{ + 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 (rmg) + { + 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 = qfalse;//CanForceJumpTo(i, c, nLDist); + + if ((nLDist < maxNeighborDist || forceJumpable) && + ((int)gWPArray[i]->origin[2] == (int)gWPArray[c]->origin[2] || forceJumpable) && + (SV_OrgVisibleBox(gWPArray[i]->origin, mins, maxs, gWPArray[c]->origin, ENTITYNUM_NONE, rmg) || 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++; + } +} + +/* +================== +SV_BotAllocateClient +================== +*/ +int SV_BotAllocateClient(void) { + int i; + client_t *cl; + + // find a client slot + for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { + if ( cl->state == CS_FREE ) { + break; + } + } + + if ( i == sv_maxclients->integer ) { + return -1; + } + + cl->gentity = SV_GentityNum( i ); + cl->gentity->s.number = i; + cl->state = CS_ACTIVE; + cl->lastPacketTime = svs.time; + cl->netchan.remoteAddress.type = NA_BOT; + cl->rate = 16384; + + return i; +} + +/* +================== +SV_BotFreeClient +================== +*/ +void SV_BotFreeClient( int clientNum ) { + client_t *cl; + + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_BotFreeClient: bad clientNum: %i", clientNum ); + } + cl = &svs.clients[clientNum]; + cl->state = CS_FREE; + cl->name[0] = 0; + if ( cl->gentity ) { + cl->gentity->r.svFlags &= ~SVF_BOT; + } +} + +/* +================== +BotDrawDebugPolygons +================== +*/ +void BotDrawDebugPolygons(void (*drawPoly)(int color, int numPoints, float *points), int value) { + static cvar_t *bot_debug, *bot_groundonly, *bot_reachability, *bot_highlightarea; + bot_debugpoly_t *poly; + int i, parm0; + + if (!debugpolygons) + return; + //bot debugging + if (!bot_debug) bot_debug = Cvar_Get("bot_debug", "0", 0); + // + if (bot_enable && bot_debug->integer) { + //show reachabilities + if (!bot_reachability) bot_reachability = Cvar_Get("bot_reachability", "0", 0); + //show ground faces only + if (!bot_groundonly) bot_groundonly = Cvar_Get("bot_groundonly", "1", 0); + //get the hightlight area + if (!bot_highlightarea) bot_highlightarea = Cvar_Get("bot_highlightarea", "0", 0); + // + parm0 = 0; + if (svs.clients[0].lastUsercmd.buttons & BUTTON_ATTACK) parm0 |= 1; + if (bot_reachability->integer) parm0 |= 2; + if (bot_groundonly->integer) parm0 |= 4; + botlib_export->BotLibVarSet("bot_highlightarea", bot_highlightarea->string); + botlib_export->Test(parm0, NULL, svs.clients[0].gentity->r.currentOrigin, + svs.clients[0].gentity->r.currentAngles); + } //end if + //draw all debug polys + for (i = 0; i < bot_maxdebugpolys; i++) { + poly = &debugpolygons[i]; + if (!poly->inuse) continue; + drawPoly(poly->color, poly->numPoints, (float *) poly->points); + //Com_Printf("poly %i, numpoints = %d\n", i, poly->numPoints); + } +} + +/* +================== +BotImport_Print +================== +*/ +void QDECL BotImport_Print(int type, char *fmt, ...) +{ + char str[2048]; + va_list ap; + + va_start(ap, fmt); + vsprintf(str, fmt, ap); + va_end(ap); + + switch(type) { + case PRT_MESSAGE: { + Com_Printf("%s", str); + break; + } + case PRT_WARNING: { + Com_Printf(S_COLOR_YELLOW "Warning: %s", str); + break; + } + case PRT_ERROR: { + Com_Printf(S_COLOR_RED "Error: %s", str); + break; + } + case PRT_FATAL: { + Com_Printf(S_COLOR_RED "Fatal: %s", str); + break; + } + case PRT_EXIT: { + Com_Error(ERR_DROP, S_COLOR_RED "Exit: %s", str); + break; + } + default: { + Com_Printf("unknown print type\n"); + break; + } + } +} + +/* +================== +BotImport_Trace +================== +*/ +void BotImport_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { + trace_t trace; + + SV_Trace(&trace, start, mins, maxs, end, passent, contentmask, qfalse, 0, 10); + //copy the trace information + bsptrace->allsolid = (qboolean)trace.allsolid; + bsptrace->startsolid = (qboolean)trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy(trace.endpos, bsptrace->endpos); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy(trace.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + +/* +================== +BotImport_EntityTrace +================== +*/ +void BotImport_EntityTrace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask) { + trace_t trace; + + SV_ClipToEntity(&trace, start, mins, maxs, end, entnum, contentmask, qfalse); + //copy the trace information + bsptrace->allsolid = (qboolean)trace.allsolid; + bsptrace->startsolid = (qboolean)trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy(trace.endpos, bsptrace->endpos); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy(trace.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + + +/* +================== +BotImport_PointContents +================== +*/ +int BotImport_PointContents(vec3_t point) { + return SV_PointContents(point, -1); +} + +/* +================== +BotImport_inPVS +================== +*/ +int BotImport_inPVS(vec3_t p1, vec3_t p2) { + return SV_inPVS (p1, p2); +} + +/* +================== +BotImport_BSPEntityData +================== +*/ +char *BotImport_BSPEntityData(void) { + return CM_EntityString(); +} + +/* +================== +BotImport_BSPModelMinsMaxsOrigin +================== +*/ +void BotImport_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t outmins, vec3_t outmaxs, vec3_t origin) { + clipHandle_t h; + vec3_t mins, maxs; + float max; + int i; + + h = CM_InlineModel(modelnum); + CM_ModelBounds(h, mins, maxs); + //if the model is rotated + if ((angles[0] || angles[1] || angles[2])) { + // expand for rotation + + max = RadiusFromBounds(mins, maxs); + for (i = 0; i < 3; i++) { + mins[i] = -max; + maxs[i] = max; + } + } + if (outmins) VectorCopy(mins, outmins); + if (outmaxs) VectorCopy(maxs, outmaxs); + if (origin) VectorClear(origin); +} + +/* +================== +BotImport_GetMemoryGame +================== +*/ +#ifndef _XBOX // These are unused, I want the tag back +void *Bot_GetMemoryGame(int size) { + void *ptr; + + ptr = Z_Malloc( size, TAG_BOTGAME, qtrue ); + + return ptr; +} + +/* +================== +BotImport_FreeMemoryGame +================== +*/ +void Bot_FreeMemoryGame(void *ptr) { + Z_Free(ptr); +} +#endif +/* +================== +BotImport_GetMemory +================== +*/ +void *BotImport_GetMemory(int size) { + void *ptr; + + ptr = Z_Malloc( size, TAG_BOTLIB, qtrue ); + return ptr; +} + +/* +================== +BotImport_FreeMemory +================== +*/ +void BotImport_FreeMemory(void *ptr) { + Z_Free(ptr); +} + +/* +================= +BotImport_HunkAlloc +================= +*/ +void *BotImport_HunkAlloc( int size ) { + if( Hunk_CheckMark() ) { + Com_Error( ERR_DROP, "SV_Bot_HunkAlloc: Alloc with marks already set\n" ); + } + return Hunk_Alloc( size, h_high ); +} + +/* +================== +BotImport_DebugPolygonCreate +================== +*/ +int BotImport_DebugPolygonCreate(int color, int numPoints, vec3_t *points) { + bot_debugpoly_t *poly; + int i; + + if (!debugpolygons) + return 0; + + for (i = 1; i < bot_maxdebugpolys; i++) { + if (!debugpolygons[i].inuse) + break; + } + if (i >= bot_maxdebugpolys) + return 0; + poly = &debugpolygons[i]; + poly->inuse = qtrue; + poly->color = color; + poly->numPoints = numPoints; + Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t)); + // + return i; +} + +/* +================== +BotImport_DebugPolygonShow +================== +*/ +void BotImport_DebugPolygonShow(int id, int color, int numPoints, vec3_t *points) { + bot_debugpoly_t *poly; + + if (!debugpolygons) return; + poly = &debugpolygons[id]; + poly->inuse = qtrue; + poly->color = color; + poly->numPoints = numPoints; + Com_Memcpy(poly->points, points, numPoints * sizeof(vec3_t)); +} + +/* +================== +BotImport_DebugPolygonDelete +================== +*/ +void BotImport_DebugPolygonDelete(int id) +{ + if (!debugpolygons) return; + debugpolygons[id].inuse = qfalse; +} + +/* +================== +BotImport_DebugLineCreate +================== +*/ +int BotImport_DebugLineCreate(void) { + vec3_t points[1]; + return BotImport_DebugPolygonCreate(0, 0, points); +} + +/* +================== +BotImport_DebugLineDelete +================== +*/ +void BotImport_DebugLineDelete(int line) { + BotImport_DebugPolygonDelete(line); +} + +/* +================== +BotImport_DebugLineShow +================== +*/ +void BotImport_DebugLineShow(int line, vec3_t start, vec3_t end, int color) { + vec3_t points[4], dir, cross, up = {0, 0, 1}; + float dot; + + VectorCopy(start, points[0]); + VectorCopy(start, points[1]); + //points[1][2] -= 2; + VectorCopy(end, points[2]); + //points[2][2] -= 2; + VectorCopy(end, points[3]); + + + 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); + + VectorNormalize(cross); + + VectorMA(points[0], 2, cross, points[0]); + VectorMA(points[1], -2, cross, points[1]); + VectorMA(points[2], -2, cross, points[2]); + VectorMA(points[3], 2, cross, points[3]); + + BotImport_DebugPolygonShow(line, color, 4, points); +} + +/* +================== +SV_BotClientCommand +================== +*/ +void BotClientCommand( int client, char *command ) { + SV_ExecuteClientCommand( &svs.clients[client], command, qtrue ); +} + +/* +================== +SV_BotFrame +================== +*/ +void SV_BotFrame( int time ) { + if (!bot_enable) return; + //NOTE: maybe the game is already shutdown + if (!gvm) return; + VM_Call( gvm, BOTAI_START_FRAME, time ); +} + +/* +=============== +SV_BotLibSetup +=============== +*/ +int SV_BotLibSetup( void ) { + if (!bot_enable) { + return 0; + } + + if ( !botlib_export ) { + Com_Printf( S_COLOR_RED "Error: SV_BotLibSetup without SV_BotInitBotLib\n" ); + return -1; + } + + return botlib_export->BotLibSetup(); +} + +/* +=============== +SV_ShutdownBotLib + +Called when either the entire server is being killed, or +it is changing to a different game directory. +=============== +*/ +int SV_BotLibShutdown( void ) { + + if ( !botlib_export ) { + return -1; + } + + return botlib_export->BotLibShutdown(); +} + +/* +================== +SV_BotInitCvars +================== +*/ +void SV_BotInitCvars(void) { + + Cvar_Get("bot_enable", "1", 0); //enable the bot + Cvar_Get("bot_developer", "0", CVAR_CHEAT); //bot developer mode + Cvar_Get("bot_debug", "0", CVAR_CHEAT); //enable bot debugging + Cvar_Get("bot_maxdebugpolys", "2", 0); //maximum number of debug polys + Cvar_Get("bot_groundonly", "1", 0); //only show ground faces of areas + Cvar_Get("bot_reachability", "0", 0); //show all reachabilities to other areas + Cvar_Get("bot_visualizejumppads", "0", CVAR_CHEAT); //show jumppads + Cvar_Get("bot_forceclustering", "0", 0); //force cluster calculations + Cvar_Get("bot_forcereachability", "0", 0); //force reachability calculations + Cvar_Get("bot_forcewrite", "0", 0); //force writing aas file + Cvar_Get("bot_aasoptimize", "0", 0); //no aas file optimisation + Cvar_Get("bot_saveroutingcache", "0", 0); //save routing cache + Cvar_Get("bot_thinktime", "100", CVAR_CHEAT); //msec the bots thinks + Cvar_Get("bot_reloadcharacters", "0", 0); //reload the bot characters each time + Cvar_Get("bot_testichat", "0", 0); //test ichats + Cvar_Get("bot_testrchat", "0", 0); //test rchats + Cvar_Get("bot_testsolid", "0", CVAR_CHEAT); //test for solid areas + Cvar_Get("bot_testclusters", "0", CVAR_CHEAT); //test the AAS clusters + Cvar_Get("bot_fastchat", "0", 0); //fast chatting bots + Cvar_Get("bot_nochat", "0", 0); //disable chats + Cvar_Get("bot_pause", "0", CVAR_CHEAT); //pause the bots thinking + Cvar_Get("bot_report", "0", CVAR_CHEAT); //get a full report in ctf + Cvar_Get("bot_grapple", "0", 0); //enable grapple + Cvar_Get("bot_rocketjump", "1", 0); //enable rocket jumping + Cvar_Get("bot_challenge", "0", 0); //challenging bot + Cvar_Get("bot_minplayers", "0", 0); //minimum players in a team or the game + Cvar_Get("bot_interbreedchar", "", CVAR_CHEAT); //bot character used for interbreeding + Cvar_Get("bot_interbreedbots", "10", CVAR_CHEAT); //number of bots used for interbreeding + Cvar_Get("bot_interbreedcycle", "20", CVAR_CHEAT); //bot interbreeding cycle + Cvar_Get("bot_interbreedwrite", "", CVAR_CHEAT); //write interbreeded bots to this file +} + +extern botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); + +// there's no such thing as this now, since the zone is unlimited, but I have to provide something +// so it doesn't run out of control alloc-wise (since the bot code calls this in a while() loop to free +// up bot mem until zone has > 1MB available again. So, simulate a reasonable limit... +// +static int bot_Z_AvailableMemory(void) +{ + const int iMaxBOTLIBMem = 8 * 1024 * 1024; // adjust accordingly. + return iMaxBOTLIBMem - Z_MemSize( TAG_BOTLIB ); +} + +/* +================== +SV_BotInitBotLib +================== +*/ +void SV_BotInitBotLib(void) { + botlib_import_t botlib_import; + + if (debugpolygons) Z_Free(debugpolygons); + bot_maxdebugpolys = Cvar_VariableIntegerValue("bot_maxdebugpolys"); + debugpolygons = (struct bot_debugpoly_s *)Z_Malloc(sizeof(bot_debugpoly_t) * bot_maxdebugpolys, TAG_BOTLIB, qtrue); + + botlib_import.Print = BotImport_Print; + botlib_import.Trace = BotImport_Trace; + botlib_import.EntityTrace = BotImport_EntityTrace; + botlib_import.PointContents = BotImport_PointContents; + botlib_import.inPVS = BotImport_inPVS; + botlib_import.BSPEntityData = BotImport_BSPEntityData; + botlib_import.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin; + botlib_import.BotClientCommand = BotClientCommand; + + //memory management + botlib_import.GetMemory = BotImport_GetMemory; + botlib_import.FreeMemory = BotImport_FreeMemory; + botlib_import.AvailableMemory = bot_Z_AvailableMemory; //Z_AvailableMemory; + botlib_import.HunkAlloc = BotImport_HunkAlloc; + + // file system access + botlib_import.FS_FOpenFile = FS_FOpenFileByMode; + botlib_import.FS_Read = FS_Read2; + botlib_import.FS_Write = FS_Write; + botlib_import.FS_FCloseFile = FS_FCloseFile; + botlib_import.FS_Seek = FS_Seek; + + //debug lines + botlib_import.DebugLineCreate = BotImport_DebugLineCreate; + botlib_import.DebugLineDelete = BotImport_DebugLineDelete; + botlib_import.DebugLineShow = BotImport_DebugLineShow; + + //debug polygons + botlib_import.DebugPolygonCreate = BotImport_DebugPolygonCreate; + botlib_import.DebugPolygonDelete = BotImport_DebugPolygonDelete; + + botlib_export = (botlib_export_t *)GetBotLibAPI( BOTLIB_API_VERSION, &botlib_import ); + assert(botlib_export); // bk001129 - somehow we end up with a zero import. +} + + +// +// * * * BOT AI CODE IS BELOW THIS POINT * * * +// + +/* +================== +SV_BotGetConsoleMessage +================== +*/ +int SV_BotGetConsoleMessage( int client, char *buf, int size ) +{ + client_t *cl; + int index; + + cl = &svs.clients[client]; + cl->lastPacketTime = svs.time; + + if ( cl->reliableAcknowledge == cl->reliableSequence ) { + return qfalse; + } + + cl->reliableAcknowledge++; + index = cl->reliableAcknowledge & ( MAX_RELIABLE_COMMANDS - 1 ); + + if ( !cl->reliableCommands[index][0] ) { + return qfalse; + } + + Q_strncpyz( buf, cl->reliableCommands[index], size ); + return qtrue; +} + +#if 0 +/* +================== +EntityInPVS +================== +*/ +int EntityInPVS( int client, int entityNum ) { + client_t *cl; + clientSnapshot_t *frame; + int i; + + cl = &svs.clients[client]; + frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; + for ( i = 0; i < frame->num_entities; i++ ) { + if ( svs.snapshotEntities[(frame->first_entity + i) % svs.numSnapshotEntities].number == entityNum ) { + return qtrue; + } + } + return qfalse; +} +#endif + +/* +================== +SV_BotGetSnapshotEntity +================== +*/ +int SV_BotGetSnapshotEntity( int client, int sequence ) { + client_t *cl; + clientSnapshot_t *frame; + + cl = &svs.clients[client]; + frame = &cl->frames[cl->netchan.outgoingSequence & PACKET_MASK]; + if (sequence < 0 || sequence >= frame->num_entities) { + return -1; + } + return svs.snapshotEntities[(frame->first_entity + sequence) % svs.numSnapshotEntities].number; +} + diff --git a/codemp/server/sv_ccmds.cpp b/codemp/server/sv_ccmds.cpp new file mode 100644 index 0000000..599ce07 --- /dev/null +++ b/codemp/server/sv_ccmds.cpp @@ -0,0 +1,1019 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "server.h" +#include "../qcommon/stringed_ingame.h" + +/* +=============================================================================== + +OPERATOR CONSOLE ONLY COMMANDS + +These commands can only be entered from stdin or by a remote operator datagram +=============================================================================== +*/ + +const char *SV_GetStringEdString(char *refSection, char *refName) +{ + /* + static char text[1024]={0}; + trap_SP_GetStringTextString(va("%s_%s", refSection, refName), text, sizeof(text)); + return text; + */ + + //Well, it would've been lovely doing it the above way, but it would mean mixing + //languages for the client depending on what the server is. So we'll mark this as + //a stringed reference with @@@ and send the refname to the client, and when it goes + //to print it will get scanned for the stringed reference indication and dealt with + //properly. + static char text[1024]={0}; + Com_sprintf(text, sizeof(text), "@@@%s", refName); + return text; +} + + + +/* +================== +SV_GetPlayerByName + +Returns the player with name from Cmd_Argv(1) +================== +*/ +static client_t *SV_GetPlayerByName( void ) { + client_t *cl; + int i; + char *s; + char cleanName[64]; + + // make sure server is running + if ( !com_sv_running->integer ) { + return NULL; + } + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "No player specified.\n" ); + return NULL; + } + + s = Cmd_Argv(1); + + // check for a name match + for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( !cl->state ) { + continue; + } + if ( !Q_stricmp( cl->name, s ) ) { + return cl; + } + + Q_strncpyz( cleanName, cl->name, sizeof(cleanName) ); + Q_CleanStr( cleanName ); + if ( !Q_stricmp( cleanName, s ) ) { + return cl; + } + } + + Com_Printf( "Player %s is not on the server\n", s ); + + return NULL; +} + +/* +================== +SV_GetPlayerByNum + +Returns the player with idnum from Cmd_Argv(1) +================== +*/ +static client_t *SV_GetPlayerByNum( void ) { + client_t *cl; + int i; + int idnum; + char *s; + + // make sure server is running + if ( !com_sv_running->integer ) { + return NULL; + } + + if ( Cmd_Argc() < 2 ) { + Com_Printf( "No player specified.\n" ); + return NULL; + } + + s = Cmd_Argv(1); + + for (i = 0; s[i]; i++) { + if (s[i] < '0' || s[i] > '9') { + Com_Printf( "Bad slot number: %s\n", s); + return NULL; + } + } + idnum = atoi( s ); + if ( idnum < 0 || idnum >= sv_maxclients->integer ) { + Com_Printf( "Bad client slot: %i\n", idnum ); + return NULL; + } + + cl = &svs.clients[idnum]; + if ( !cl->state ) { + Com_Printf( "Client %i is not active\n", idnum ); + return NULL; + } + return cl; +} + +//========================================================= + + + +/* +================== +SV_Map_f + +Restart the server on a different map +================== +*/ +static void SV_Map_f( void ) { + char *cmd; + char *map; + qboolean killBots, cheat; + char expanded[MAX_QPATH]; + char mapname[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 + Com_sprintf (expanded, sizeof(expanded), "maps/%s.bsp", map); + if ( FS_ReadFile (expanded, NULL) == -1 ) { + Com_Printf ("Can't find map %s\n", expanded); + return; + } +#endif + + // force latched values to get set + Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH ); + + cmd = Cmd_Argv(0); + if( Q_stricmpn( cmd, "sp", 2 ) == 0 ) { + Cvar_SetValue( "g_gametype", GT_SINGLE_PLAYER ); + Cvar_SetValue( "g_doWarmup", 0 ); + // may not set sv_maxclients directly, always set latched + Cvar_SetLatched( "sv_maxclients", "8" ); + cmd += 2; + cheat = qfalse; + killBots = qtrue; + } + else { + if ( !Q_stricmpn( cmd, "devmap",6 ) || !Q_stricmp( cmd, "spdevmap" ) ) { + cheat = qtrue; + killBots = qtrue; + } else { + cheat = qfalse; + killBots = qfalse; + } + /* + if( sv_gametype->integer == GT_SINGLE_PLAYER ) { + Cvar_SetValue( "g_gametype", GT_FFA ); + } + */ + } + + // save the map name here cause on a map restart we reload the jampconfig.cfg + // and thus nuke the arguments of the map command + Q_strncpyz(mapname, map, sizeof(mapname)); + + ForceReload_e eForceReload = eForceReload_NOTHING; // default for normal load + +// if ( !Q_stricmp( cmd, "devmapbsp") ) { // not relevant in MP codebase +// eForceReload = eForceReload_BSP; +// } +// else + if ( !Q_stricmp( cmd, "devmapmdl") ) { + eForceReload = eForceReload_MODELS; + } + else + if ( !Q_stricmp( cmd, "devmapall") ) { + eForceReload = eForceReload_ALL; + } + + // start up the map + SV_SpawnServer( mapname, killBots, 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 ( cheat ) { + Cvar_Set( "sv_cheats", "1" ); + } else { + Cvar_Set( "sv_cheats", "0" ); + } +} + + +/* +================ +SV_MapRestart_f + +Completely restarts a level, but doesn't send a new gamestate to the clients. +This allows fair starts with variable load times. +================ +*/ +static void SV_MapRestart_f( void ) { + int i; + client_t *client; + char *denied; + qboolean isBot; + int delay; + + // make sure we aren't restarting twice in the same frame + if ( com_frameTime == sv.serverId ) { + return; + } + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( sv.restartTime ) { + return; + } + + if (Cmd_Argc() > 1 ) { + delay = atoi( Cmd_Argv(1) ); + } + else { + delay = 5; + } + if( delay ) { + sv.restartTime = svs.time + delay * 1000; + SV_SetConfigstring( CS_WARMUP, va("%i", sv.restartTime) ); + return; + } + + // check for changes in variables that can't just be restarted + // check for maxclients change + if ( sv_maxclients->modified || sv_gametype->modified ) { + char mapname[MAX_QPATH]; + + Com_Printf( "variable change -- restarting.\n" ); + // restart the map the slow way + Q_strncpyz( mapname, Cvar_VariableString( "mapname" ), sizeof( mapname ) ); + + SV_SpawnServer( mapname, qfalse, eForceReload_NOTHING ); + return; + } + + // toggle the server bit so clients can detect that a + // map_restart has happened + svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; + + // generate a new serverid + sv.restartedServerId = sv.serverId; + sv.serverId = com_frameTime; + Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); + + // reset all the vm data in place without changing memory allocation + // note that we do NOT set sv.state = SS_LOADING, so configstrings that + // had been changed from their default values will generate broadcast updates + sv.state = SS_LOADING; + sv.restarting = qtrue; + + SV_RestartGameProgs(); + + // run a few frames to allow everything to settle + for ( i = 0 ;i < 3 ; i++ ) { + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + svs.time += 100; + } + + sv.state = SS_GAME; + sv.restarting = qfalse; + + // connect and begin all the clients + for (i=0 ; iinteger ; i++) { + client = &svs.clients[i]; + + // send the new gamestate to all connected clients + if ( client->state < CS_CONNECTED) { + continue; + } + + if ( client->netchan.remoteAddress.type == NA_BOT ) { + isBot = qtrue; + } else { + isBot = qfalse; + } + + // add the map_restart command + SV_AddServerCommand( client, "map_restart\n" ); + + // connect the client again, without the firstTime flag + denied = (char *)VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( client, denied ); + Com_Printf( "SV_MapRestart_f(%d): dropped client %i - denied!\n", delay, i ); // bk010125 + continue; + } + + client->state = CS_ACTIVE; + + SV_ClientEnterWorld( client, &client->lastUsercmd ); + } + + // run another frame to allow things to look at all the players + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + svs.time += 100; +} + +//=============================================================== + +/* +================== +SV_GetPlayerByName + +Returns the player with name from Cmd_Argv(1) +================== +*/ +static client_t *SV_GetPlayerByFedName( const char *name ) +{ + client_t *cl; + int i; + char cleanName[64]; + + // make sure server is running + if ( !com_sv_running->integer ) + { + return NULL; + } + + // check for a name match + for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) + { + if ( !cl->state ) + { + continue; + } + if ( !Q_stricmp( cl->name, name ) ) + { + return cl; + } + + Q_strncpyz( cleanName, cl->name, sizeof(cleanName) ); + Q_CleanStr( cleanName ); + if ( !Q_stricmp( cleanName, name ) ) + { + return cl; + } + } + + return NULL; +} + +static void SV_KickByName( const char *name ) +{ + client_t *cl; + int i; + + // make sure server is running + if ( !com_sv_running->integer ) + { + return; + } + + cl = SV_GetPlayerByFedName(name); + if ( !cl ) + { + if ( !Q_stricmp(name, "all") ) + { + for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) + { + if ( !cl->state ) + { + continue; + } + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) + { + continue; + } + SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie + } + } + else if ( !Q_stricmp(name, "allbots") ) + { + for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) + { + if ( !cl->state ) + { + continue; + } + if( cl->netchan.remoteAddress.type != NA_BOT ) + { + continue; + } + SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie + } + } + return; + } + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) + { +// SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + SV_SendServerCommand(NULL, "print \"%s\"", SV_GetStringEdString("MP_SVGAME","CANNOT_KICK_HOST")); + return; + } + + SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie +} + +/* +================== +SV_Kick_f + +Kick a user off of the server FIXME: move to game +================== +*/ +static void SV_Kick_f( void ) { + client_t *cl; + int i; + + // 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: kick \nkick all = kick everyone\nkick allbots = kick all bots\n"); + return; + } + + if (!Q_stricmp(Cmd_Argv(1), "Padawan")) + { //if you try to kick the default name, also try to kick "" + SV_KickByName(""); + } + + cl = SV_GetPlayerByName(); + if ( !cl ) { + if ( !Q_stricmp(Cmd_Argv(1), "all") ) { + for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( !cl->state ) { + continue; + } + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { + continue; + } + SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie + } + } + else if ( !Q_stricmp(Cmd_Argv(1), "allbots") ) { + for ( i=0, cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++ ) { + if ( !cl->state ) { + continue; + } + if( cl->netchan.remoteAddress.type != NA_BOT ) { + continue; + } + SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie + } + } + return; + } + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { +// SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + SV_SendServerCommand(NULL, "print \"%s\"", SV_GetStringEdString("MP_SVGAME","CANNOT_KICK_HOST")); + return; + } + + SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie +} + +/* +================== +SV_Ban_f + +Ban a user from being able to play on this server through the auth +server +================== +*/ +#ifdef USE_CD_KEY + +static void SV_Ban_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: banUser \n"); + return; + } + + cl = SV_GetPlayerByName(); + + if (!cl) { + return; + } + + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { +// SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + SV_SendServerCommand(NULL, "print \"%s\"", SV_GetStringEdString("MP_SVGAME","CANNOT_KICK_HOST")); + return; + } + + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], + cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); + Com_Printf("%s was banned from coming back\n", cl->name); + } +} + +/* +================== +SV_BanNum_f + +Ban a user from being able to play on this server through the auth +server +================== +*/ +static void SV_BanNum_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: banClient \n"); + return; + } + + cl = SV_GetPlayerByNum(); + if ( !cl ) { + return; + } + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { +// SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + SV_SendServerCommand(NULL, "print \"%s\"", SV_GetStringEdString("MP_SVGAME","CANNOT_KICK_HOST")); + return; + } + + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], + cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); + Com_Printf("%s was banned from coming back\n", cl->name); + } +} + +#endif // USE_CD_KEY + +/* +================== +SV_KickNum_f + +Kick a user off of the server FIXME: move to game +================== +*/ +static void SV_KickNum_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: kicknum \n"); + return; + } + + cl = SV_GetPlayerByNum(); + if ( !cl ) { + return; + } + if( cl->netchan.remoteAddress.type == NA_LOOPBACK ) { +// SV_SendServerCommand(NULL, "print \"%s\"", "Cannot kick host player\n"); + SV_SendServerCommand(NULL, "print \"%s\"", SV_GetStringEdString("MP_SVGAME","CANNOT_KICK_HOST")); + return; + } + + SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","WAS_KICKED")); // "was kicked" ); + cl->lastPacketTime = svs.time; // in case there is a funny zombie +} + +/* +================ +SV_Status_f +================ +*/ +static void SV_Status_f( void ) +{ + int i; + client_t *cl; + playerState_t *ps; + const char *s; + int ping; + char state[32]; + qboolean avoidTruncation = qfalse; + + // make sure server is running + if ( !com_sv_running->integer ) + { + Com_Printf( SE_GetString("STR_SERVER_SERVER_NOT_RUNNING") ); + return; + } + + if ( Cmd_Argc() > 1 ) + { + if (!Q_stricmp("notrunc", Cmd_Argv(1))) + { + avoidTruncation = qtrue; + } + } + + 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 < sv_maxclients->integer ; i++,cl++) + { + if (!cl->state) + { + continue; + } + + if (cl->state == CS_CONNECTED) + { + strcpy(state, "CNCT "); + } + else if (cl->state == CS_ZOMBIE) + { + strcpy(state, "ZMBI "); + } + else + { + ping = cl->ping < 9999 ? cl->ping : 9999; + sprintf(state, "%4i", ping); + } + + ps = SV_GameClientNum( i ); + s = NET_AdrToString( cl->netchan.remoteAddress ); + + if (!avoidTruncation) + { + Com_Printf ("%3i %5i %s %-15.15s %7i %21s %5i %5i\n", + i, + ps->persistant[PERS_SCORE], + state, + cl->name, + svs.time - cl->lastPacketTime, + s, + cl->netchan.qport, + cl->rate + ); + } + else + { + Com_Printf ("%3i %5i %s %s %7i %21s %5i %5i\n", + i, + ps->persistant[PERS_SCORE], + state, + cl->name, + svs.time - cl->lastPacketTime, + s, + cl->netchan.qport, + cl->rate + ); + } + } + Com_Printf ("\n"); +} + +/* +================== +SV_ConSay_f +================== +*/ +static void SV_ConSay_f(void) { + char *p; + char text[1024]; + + if( !com_dedicated->integer ) { + Com_Printf( "Server is not dedicated.\n" ); + return; + } + + // make sure server is running + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + return; + } + + if ( Cmd_Argc () < 2 ) { + return; + } + + strcpy (text, "Server: "); + p = Cmd_Args(); + + if ( *p == '"' ) { + p++; + p[strlen(p)-1] = 0; + } + + strcat(text, p); + + SV_SendServerCommand(NULL, "chat \"%s\n\"", text); +} + +static const char *forceToggleNamePrints[] = +{ + "HEAL",//FP_HEAL + "JUMP",//FP_LEVITATION + "SPEED",//FP_SPEED + "PUSH",//FP_PUSH + "PULL",//FP_PULL + "MINDTRICK",//FP_TELEPTAHY + "GRIP",//FP_GRIP + "LIGHTNING",//FP_LIGHTNING + "DARK RAGE",//FP_RAGE + "PROTECT",//FP_PROTECT + "ABSORB",//FP_ABSORB + "TEAM HEAL",//FP_TEAM_HEAL + "TEAM REPLENISH",//FP_TEAM_FORCE + "DRAIN",//FP_DRAIN + "SEEING",//FP_SEE + "SABER OFFENSE",//FP_SABER_OFFENSE + "SABER DEFENSE",//FP_SABER_DEFENSE + "SABER THROW",//FP_SABERTHROW + NULL +}; + +/* +================== +SV_ForceToggle_f +================== +*/ +void SV_ForceToggle_f(void) +{ + int i = 0; + int fpDisabled = Cvar_VariableValue("g_forcePowerDisable"); + int targetPower = 0; + const char *powerDisabled = "Enabled"; + + if ( Cmd_Argc () < 2 ) + { //no argument supplied, spit out a list of force powers and their numbers + while (i < NUM_FORCE_POWERS) + { + if (fpDisabled & (1 << i)) + { + powerDisabled = "Disabled"; + } + else + { + powerDisabled = "Enabled"; + } + + Com_Printf(va("%i - %s - Status: %s\n", i, forceToggleNamePrints[i], powerDisabled)); + i++; + } + + Com_Printf("Example usage: forcetoggle 3\n(toggles PUSH)\n"); + return; + } + + targetPower = atoi(Cmd_Argv(1)); + + if (targetPower < 0 || targetPower >= NUM_FORCE_POWERS) + { + Com_Printf("Specified a power that does not exist.\nExample usage: forcetoggle 3\n(toggles PUSH)\n"); + return; + } + + if (fpDisabled & (1 << targetPower)) + { + powerDisabled = "enabled"; + fpDisabled &= ~(1 << targetPower); + } + else + { + powerDisabled = "disabled"; + fpDisabled |= (1 << targetPower); + } + + Cvar_Set("g_forcePowerDisable", va("%i", fpDisabled)); + + Com_Printf("%s has been %s.\n", forceToggleNamePrints[targetPower], powerDisabled); +} + +/* +================== +SV_Heartbeat_f + +Also called by SV_DropClient, SV_DirectConnect, and SV_SpawnServer +================== +*/ +void SV_Heartbeat_f( void ) { + svs.nextHeartbeatTime = -9999999; +} + + +/* +=========== +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 ) ); + if ( !com_sv_running->integer ) { + Com_Printf( "Server is not running.\n" ); + } +} + + +/* +=========== +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_GetPlayerByName(); + if ( !cl ) { + return; + } + + Com_Printf( "userinfo\n" ); + Com_Printf( "--------\n" ); + Info_Print( cl->userinfo ); +} + + +/* +================= +SV_KillServer +================= +*/ +static void SV_KillServer_f( void ) { + SV_Shutdown( "killserver" ); +} + +//=========================================================== + +/* +================== +SV_AddOperatorCommands +================== +*/ +void SV_AddOperatorCommands( void ) { + static qboolean initialized; + + if ( initialized ) { + return; + } + initialized = qtrue; + + Cmd_AddCommand ("heartbeat", SV_Heartbeat_f); + Cmd_AddCommand ("kick", SV_Kick_f); +#ifdef USE_CD_KEY + Cmd_AddCommand ("banUser", SV_Ban_f); + Cmd_AddCommand ("banClient", SV_BanNum_f); +#endif // USE_CD_KEY + + Cmd_AddCommand ("clientkick", SV_KickNum_f); + 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 ("map_restart", SV_MapRestart_f); + Cmd_AddCommand ("sectorlist", SV_SectorList_f); + Cmd_AddCommand ("map", SV_Map_f); +#ifndef PRE_RELEASE_DEMO + Cmd_AddCommand ("devmap", SV_Map_f); + Cmd_AddCommand ("spmap", SV_Map_f); + Cmd_AddCommand ("spdevmap", SV_Map_f); +// Cmd_AddCommand ("devmapbsp", SV_Map_f); // not used in MP codebase, no server BSP_cacheing + Cmd_AddCommand ("devmapmdl", SV_Map_f); + Cmd_AddCommand ("devmapall", SV_Map_f); +#endif + Cmd_AddCommand ("killserver", SV_KillServer_f); +// if( com_dedicated->integer ) + { + Cmd_AddCommand ("svsay", SV_ConSay_f); + } + + Cmd_AddCommand ("forcetoggle", SV_ForceToggle_f); +} + +/* +================== +SV_RemoveOperatorCommands +================== +*/ +void SV_RemoveOperatorCommands( void ) { +#if 0 + // removing these won't let the server start again + Cmd_RemoveCommand ("heartbeat"); + Cmd_RemoveCommand ("kick"); + Cmd_RemoveCommand ("banUser"); + Cmd_RemoveCommand ("banClient"); + Cmd_RemoveCommand ("status"); + Cmd_RemoveCommand ("serverinfo"); + Cmd_RemoveCommand ("systeminfo"); + Cmd_RemoveCommand ("dumpuser"); + Cmd_RemoveCommand ("map_restart"); + Cmd_RemoveCommand ("sectorlist"); + Cmd_RemoveCommand ("svsay"); +#endif +} + diff --git a/codemp/server/sv_client.cpp b/codemp/server/sv_client.cpp new file mode 100644 index 0000000..4f24b30 --- /dev/null +++ b/codemp/server/sv_client.cpp @@ -0,0 +1,1855 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// sv_client.c -- server code for dealing with clients + +#include "server.h" +#include "../qcommon/stringed_ingame.h" +#include "../RMG/RM_Headers.h" +#include "../zlib32/zip.h" + +static void SV_CloseDownload( client_t *cl ); + +/* +================= +SV_GetChallenge + +A "getchallenge" OOB command has been received +Returns a challenge number that can be used +in a subsequent connectResponse command. +We do this to prevent denial of service attacks that +flood the server with invalid connection IPs. With a +challenge, they must give a valid IP address. + +If we are authorizing, a challenge request will cause a packet +to be sent to the authorize server. + +When an authorizeip is returned, a challenge response will be +sent to that ip. +================= +*/ +void SV_GetChallenge( netadr_t from ) { + int i; + int oldest; + int oldestTime; + challenge_t *challenge; + + // ignore if we are in single player + /* + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) { + return; + } + */ + if (Cvar_VariableValue("ui_singlePlayerActive")) + { + return; + } + + oldest = 0; + oldestTime = 0x7fffffff; + + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { + if ( !challenge->connected && NET_CompareAdr( from, challenge->adr ) ) { + break; + } + if ( challenge->time < oldestTime ) { + oldestTime = challenge->time; + oldest = i; + } + } + + if (i == MAX_CHALLENGES) { + // this is the first time this client has asked for a challenge + challenge = &svs.challenges[oldest]; + + challenge->challenge = ( (rand() << 16) ^ rand() ) ^ svs.time; + challenge->adr = from; + challenge->firstTime = svs.time; + challenge->time = svs.time; + challenge->connected = qfalse; + i = oldest; + } + + // if they are on a lan address, send the challengeResponse immediately + if ( Sys_IsLANAddress( from ) ) { + challenge->pingTime = svs.time; + NET_OutOfBandPrint( NS_SERVER, from, "challengeResponse %i", challenge->challenge ); + return; + } + +#ifdef USE_CD_KEY + // look up the authorize server's IP + if ( !svs.authorizeAddress.ip[0] && svs.authorizeAddress.type != NA_BAD ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &svs.authorizeAddress ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + svs.authorizeAddress.ip[0], svs.authorizeAddress.ip[1], + svs.authorizeAddress.ip[2], svs.authorizeAddress.ip[3], + BigShort( svs.authorizeAddress.port ) ); + } + + // if they have been challenging for a long time and we + // haven't heard anything from the authoirze server, go ahead and + // let them in, assuming the id server is down + if ( svs.time - challenge->firstTime > AUTHORIZE_TIMEOUT ) { + Com_DPrintf( "authorize server timed out\n" ); + + challenge->pingTime = svs.time; + NET_OutOfBandPrint( NS_SERVER, challenge->adr, + "challengeResponse %i", challenge->challenge ); + return; + } + + // otherwise send their ip to the authorize server + if ( svs.authorizeAddress.type != NA_BAD ) { + cvar_t *fs; + char game[1024]; + + game[0] = 0; + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (fs && fs->string[0] != 0) { + strcpy(game, fs->string); + } + Com_DPrintf( "sending getIpAuthorize for %s\n", NET_AdrToString( from )); + fs = Cvar_Get ("sv_allowAnonymous", "0", CVAR_SERVERINFO); + + NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, + "getIpAuthorize %i %i.%i.%i.%i %s %s", svs.challenges[i].challenge, + from.ip[0], from.ip[1], from.ip[2], from.ip[3], game, fs->integer ); + } +#else + challenge->pingTime = svs.time; + NET_OutOfBandPrint( NS_SERVER, challenge->adr, "challengeResponse %i", challenge->challenge ); +#endif // USE_CD_KEY +} + +/* +==================== +SV_AuthorizeIpPacket + +A packet has been returned from the authorize server. +If we have a challenge adr for that ip, send the +challengeResponse to it +==================== +*/ +#ifndef _XBOX // No authorization on Xbox +void SV_AuthorizeIpPacket( netadr_t from ) { + int challenge; + int i; + char *s; + char *r; + char ret[1024]; + + if ( !NET_CompareBaseAdr( from, svs.authorizeAddress ) ) { + Com_Printf( "SV_AuthorizeIpPacket: not from authorize server\n" ); + return; + } + + challenge = atoi( Cmd_Argv( 1 ) ); + + for (i = 0 ; i < MAX_CHALLENGES ; i++) { + if ( svs.challenges[i].challenge == challenge ) { + break; + } + } + if ( i == MAX_CHALLENGES ) { + Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" ); + return; + } + + // send a packet back to the original client + svs.challenges[i].pingTime = svs.time; + s = Cmd_Argv( 2 ); + r = Cmd_Argv( 3 ); // reason + + if ( !Q_stricmp( s, "demo" ) ) { + if ( Cvar_VariableValue( "fs_restrict" ) ) { + // a demo client connecting to a demo server + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, + "challengeResponse %i", svs.challenges[i].challenge ); + return; + } + // they are a demo client trying to connect to a real server + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nServer is not a demo server\n" ); + // clear the challenge record so it won't timeout and let them through + Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + return; + } + if ( !Q_stricmp( s, "accept" ) ) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, + "challengeResponse %i", svs.challenges[i].challenge ); + return; + } + if ( !Q_stricmp( s, "unknown" ) ) { + if (!r) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nAwaiting CD key authorization\n" ); + } else { + sprintf(ret, "print\n%s\n", r); + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); + } + // clear the challenge record so it won't timeout and let them through + Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); + return; + } + + // authorization failed + if (!r) { + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "print\nSomeone is using this CD Key\n" ); + } else { + sprintf(ret, "print\n%s\n", r); + NET_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, ret ); + } + + // clear the challenge record so it won't timeout and let them through + Com_Memset( &svs.challenges[i], 0, sizeof( svs.challenges[i] ) ); +} +#endif + +/* +================== +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; + sharedEntity_t *ent; + int clientNum; + int version; + int qport; + int challenge; + char *password; + int startIndex; + char *denied; + int count; + bool reconnect = false; + + 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; + } + + challenge = atoi( Info_ValueForKey( userinfo, "challenge" ) ); + qport = atoi( Info_ValueForKey( userinfo, "qport" ) ); + + // quick reject + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + +/* This was preventing sv_reconnectlimit from working. It seems like commenting this + out has solved the problem. HOwever, if there is a future problem then it could + be this. + + if ( cl->state == CS_FREE ) { + continue; + } +*/ + + if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) + && ( cl->netchan.qport == qport + || from.port == cl->netchan.remoteAddress.port ) ) { + if (( svs.time - cl->lastConnectTime) + < (sv_reconnectlimit->integer * 1000)) { + NET_OutOfBandPrint( NS_SERVER, from, "print\nReconnect rejected : too soon\n" ); + Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); + return; + } + break; + } + } + + // see if the challenge is valid (LAN clients don't need to challenge) + if ( !NET_IsLocalAddress (from) ) { + int ping; + + for (i=0 ; ivalue && ping < sv_minPing->value ) { + // don't let them keep trying until they get a big delay + NET_OutOfBandPrint( NS_SERVER, from, va("print\n%s\n", SE_GetString("MP_SVGAME", "SERVER_FOR_HIGH_PING")));//Server is for high pings only\n" ); + Com_DPrintf (SE_GetString("MP_SVGAME", "CLIENT_REJECTED_LOW_PING"), i);//"Client %i rejected on a too low ping\n", i); + // reset the address otherwise their ping will keep increasing + // with each connect message and they'd eventually be able to connect + svs.challenges[i].adr.port = 0; + return; + } + if ( sv_maxPing->value && ping > sv_maxPing->value ) { + NET_OutOfBandPrint( NS_SERVER, from, va("print\n%s\n", SE_GetString("MP_SVGAME", "SERVER_FOR_LOW_PING")));//Server is for low pings only\n" ); + Com_DPrintf (SE_GetString("MP_SVGAME", "CLIENT_REJECTED_HIGH_PING"), i);//"Client %i rejected on a too high ping\n", i); + return; + } + } + } else { + // force the "ip" info key to "localhost" + Info_SetValueForKey( userinfo, "ip", "localhost" ); + } + + newcl = &temp; + Com_Memset (newcl, 0, sizeof(client_t)); + + // if there is already a slot for this ip, reuse it + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; 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 ) ) { + Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); + newcl = cl; + reconnect = true; + // VVFIXME - both SOF2 and Wolf remove this call, claiming it blows away the user's info + // disconnect the client from the game first so any flags the + // player might have are dropped + VM_Call( gvm, GAME_CLIENT_DISCONNECT, newcl - svs.clients ); + // + goto gotnewcl; + } + } + + // find a client slot + // if "sv_privateClients" is set > 0, then that number + // of client slots will be reserved for connections that + // have "password" set to the value of "sv_privatePassword" + // Info requests will report the maxclients as if the private + // slots didn't exist, to prevent people from trying to connect + // to a full server. + // This is to allow us to reserve a couple slots here on our + // servers so we can play without having to kick people. + + // check for privateClient password + password = Info_ValueForKey( userinfo, "password" ); +#ifdef _XBOX // We don't do private slots quite the same + startIndex = 0; +#else + if ( !strcmp( password, sv_privatePassword->string ) ) { + startIndex = 0; + } else { + // skip past the reserved slots + startIndex = sv_privateClients->integer; + } +#endif + + newcl = NULL; + for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if (cl->state == CS_FREE) { + newcl = cl; + break; + } + } + + if ( !newcl ) { + if ( NET_IsLocalAddress( from ) ) { + count = 0; + for ( i = startIndex; i < sv_maxclients->integer ; i++ ) { + cl = &svs.clients[i]; + if (cl->netchan.remoteAddress.type == NA_BOT) { + count++; + } + } + // if they're all bots + if (count >= sv_maxclients->integer - startIndex) { + SV_DropClient(&svs.clients[sv_maxclients->integer - 1], "only bots on server"); + newcl = &svs.clients[sv_maxclients->integer - 1]; + } + else { + Com_Error( ERR_FATAL, "server is full on local connect\n" ); + return; + } + } + else { + const char *SV_GetStringEdString(char *refSection, char *refName); + NET_OutOfBandPrint( NS_SERVER, from, va("print\n%s\n", SV_GetStringEdString("MP_SVGAME","SERVER_IS_FULL"))); + Com_DPrintf ("Rejected a connection.\n"); + return; + } + } + + // we got a newcl, so reset the reliableSequence and reliableAcknowledge + cl->reliableAcknowledge = 0; + cl->reliableSequence = 0; + +gotnewcl: + +#ifdef _XBOX + // OK. We used to manually search for a spot in the xbOnlineInfo player list now. + // But we MUST keep svs.clients in sync with the player list, so that clients + // can keep their cgs.clientinfo in sync as well. So... + int index = newcl - svs.clients; + + // Sanity check + assert( index >= 0 && index < MAX_ONLINE_PLAYERS ); + + // Avoid a nasty bug situation: if the client is not logged on, they didn't send + // an XUID. Don't let live clients on syslink servers or vice-versa. Localhost + // doesn't need this check. + const char *sxuid = Info_ValueForKey( userinfo, "xuid" ); + if ( !NET_IsLocalAddress(from) && ((logged_on && !*sxuid) || (!logged_on && *sxuid)) ) + { + // We may not be able to reply to them - we might not have their address registered + NET_OutOfBandPrint( NS_SERVER, from, "print\nLive/SystemLink mismatch\n" ); + return; + } + + if ( reconnect ) + { + // This is a reconnect message. They're going into the same slot as before. + + // We don't need to grab much off the net here. Everything should still be valid. + // In particular, keep the old refIndex. If they sent an XUID, we'll update that, + // but if not we want to continue using their fake one from earlier. + if (logged_on) + { + StringToXUID(&xbOnlineInfo.xbPlayerList[index].xuid, sxuid); + } + + // Ensure the client is active + xbOnlineInfo.xbPlayerList[index].isActive = true; + } + else + { + // OK. New client. First, sanity check that player and client lists are in sync: + assert( !xbOnlineInfo.xbPlayerList[index].isActive ); + + // Again, we don't have a full PlayerInfo like SOF2, but this is fine + XBPlayerInfo *pPlayer = &xbOnlineInfo.xbPlayerList[index]; + + // Zero it out to start with + memset(pPlayer, 0, sizeof(XBPlayerInfo)); + + // Copy XNADDR + StringToXnAddr(&pPlayer->xbAddr, Info_ValueForKey( userinfo, "xnaddr" )); + + // Copy XUID + if(logged_on) + { + StringToXUID(&pPlayer->xuid, sxuid); + } + else + { + // Users have no XUID if not logged on. We make one up. + pPlayer->xuid.qwUserID = svs.clientRefNum; + } + + pPlayer->refIndex = svs.clientRefNum++; + pPlayer->isActive = true; + } + + // One more thing we need - the client will send an xbps "1" if they can + // use a private slot (XBox Private Slot). Just check for its existence... + bool usePrivateSlot = (strcmp( Info_ValueForKey( userinfo, "xbps" ), "1" ) == 0); + + // Set our refIndex for later. We also remember if they were a private slot + // candidate when they joined, so we can free up the right type of slot when + // they leave later. + temp.refIndex = xbOnlineInfo.xbPlayerList[index].refIndex; + temp.usePrivateSlot = usePrivateSlot; + + // Remove any mute flags as they are local to the client. + xbOnlineInfo.xbPlayerList[index].flags &= ~MUTED_PLAYER; + xbOnlineInfo.xbPlayerList[index].flags &= ~REMOTE_MUTED; + + // Disable new player's voice until they send their VOICESTATE + xbOnlineInfo.xbPlayerList[index].flags &= ~VOICE_CAN_RECV; + xbOnlineInfo.xbPlayerList[index].flags &= ~VOICE_CAN_SEND; + + // Now send the new client's info to all other relevant clients + for (int j = 0; j < sv_maxclients->integer; j++) { + if ( svs.clients[j].state < CS_PRIMED ) { + continue; + } + if (svs.clients[j].netchan.remoteAddress.type == NA_BOT) { + continue; + } + SV_SendClientNewPeer( &svs.clients[j], &xbOnlineInfo.xbPlayerList[index] ); + } +#endif + + // 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 challenge + newcl->challenge = challenge; + + // 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 = (char *)VM_Call( gvm, GAME_CLIENT_CONNECT, clientNum, qtrue, qfalse ); // firstTime = qtrue + if ( denied ) { + // we can't just use VM_ArgPtr, because that is only valid inside a VM_Call + denied = (char *)VM_ExplicitArgPtr( gvm, (int)denied ); + + NET_OutOfBandPrint( NS_SERVER, from, "print\n%s\n", denied ); + Com_DPrintf ("Game rejected a connection: %s.\n", denied); + return; + } + + SV_UserinfoChanged( newcl ); + + // update the advertised session + // +#ifdef _XBOX + if (!reconnect) + XBL_MM_AddPlayer( usePrivateSlot ); +#endif + + // send the connect packet to the client + NET_OutOfBandPrint( NS_SERVER, from, "connectResponse" ); + + Com_DPrintf( "Going from CS_FREE to CS_CONNECTED for %s\n", newcl->name ); + + newcl->state = CS_CONNECTED; + newcl->nextSnapshotTime = svs.time; + newcl->lastPacketTime = svs.time; + newcl->lastConnectTime = svs.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; + + newcl->lastUserInfoChange = 0; //reset the delay + newcl->lastUserInfoCount = 0; //reset the count + + // if this was the first client on the server, or the last client + // the server can hold, send a heartbeat to the master. + count = 0; + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + if ( count == 1 || count == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +} + + +/* +===================== +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 ) { + int i; + challenge_t *challenge; + + if ( drop->state == CS_ZOMBIE ) { + return; // already dropped + } + + if ( !drop->gentity || !(drop->gentity->r.svFlags & SVF_BOT) ) { + // see if we already have a challenge for this ip + challenge = &svs.challenges[0]; + + for (i = 0 ; i < MAX_CHALLENGES ; i++, challenge++) { + if ( NET_CompareAdr( drop->netchan.remoteAddress, challenge->adr ) ) { + challenge->connected = qfalse; + break; + } + } + } + +#ifdef _XBOX + // Tells all clients to remove the dropped player from their list, if not a bot + if ( drop->netchan.remoteAddress.type != NA_BOT ) + { + int index = drop - svs.clients; + for (int j = 0; j < sv_maxclients->integer ; j++) { + if ( svs.clients[j].state < CS_PRIMED ) { + continue; + } + if ( svs.clients[j].netchan.remoteAddress.type == NA_BOT ) { + continue; + } + SV_SendClientRemovePeer(&svs.clients[j], index); + } + + // update the advertised session + XBL_MM_RemovePlayer( drop->usePrivateSlot ); + } +#endif + + // Kill any download +#ifndef _XBOX // No downloads on Xbox + SV_CloseDownload( drop ); +#endif + + // tell everyone why they got dropped + SV_SendServerCommand( NULL, "print \"%s" S_COLOR_WHITE " %s\n\"", drop->name, reason ); + + Com_DPrintf( "Going to CS_ZOMBIE for %s\n", drop->name ); + drop->state = CS_ZOMBIE; // become free in a few seconds + +#ifndef _XBOX // No downloads on Xbox + if (drop->download) { + FS_FCloseFile( drop->download ); + drop->download = 0; + } +#endif + + // call the prog function for removing a client + // this will remove the body, among other things + VM_Call( gvm, GAME_CLIENT_DISCONNECT, drop - svs.clients ); + + // add the disconnect command + SV_SendServerCommand( drop, va("disconnect \"%s\"", reason ) ); + + if ( drop->netchan.remoteAddress.type == NA_BOT ) { + SV_BotFreeClient( drop - svs.clients ); + } + + // nuke user info + SV_SetUserinfo( drop - svs.clients, "" ); + + // if this was the last client on the server, send a heartbeat + // to the master so it is known the server is empty + // send a heartbeat now so the master will get up to date info + // if there is already a slot for this ip, reuse it +#ifndef _XBOX // No master on Xbox + for (i=0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + break; + } + } + if ( i == sv_maxclients->integer ) { + SV_Heartbeat_f(); + } +#endif +} + +void SV_WriteRMGAutomapSymbols ( msg_t* msg ) +{ + int count = TheRandomMissionManager->GetAutomapSymbolCount ( ); + int i; + + MSG_WriteShort ( msg, count ); + + for ( i = 0; i < count; i ++ ) + { + rmAutomapSymbol_t* symbol = TheRandomMissionManager->GetAutomapSymbol ( i ); + + MSG_WriteByte ( msg, symbol->mType ); + MSG_WriteByte ( msg, symbol->mSide ); + MSG_WriteLong ( msg, (long)symbol->mOrigin[0] ); + MSG_WriteLong ( msg, (long)symbol->mOrigin[1] ); + } +} + +/* +================ +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; + entityState_t *base, nullstate; + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + // MW - my attempt to fix illegible server message errors caused by + // packet fragmentation of initial snapshot. + while(client->state&&client->netchan.unsentFragments) + { + // send additional message fragments if the last message + // was too large to send at once + + Com_Printf ("[ISM]SV_SendClientGameState() [2] for %s, writing out old fragments\n", client->name); + SV_Netchan_TransmitNextFragment(&client->netchan); + } + + Com_DPrintf ("SV_SendClientGameState() for %s\n", client->name); + Com_DPrintf( "Going from CS_CONNECTED to CS_PRIMED for %s\n", client->name ); + client->state = CS_PRIMED; + client->pureAuthentic = 0; + + // 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; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // 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_WriteBigString( &msg, sv.configstrings[start] ); + } + } + + // write the baselines + Com_Memset( &nullstate, 0, sizeof( nullstate ) ); + for ( start = 0 ; start < MAX_GENTITIES; start++ ) { + base = &sv.svEntities[start].baseline; + if ( !base->number ) { + continue; + } + MSG_WriteByte( &msg, svc_baseline ); + MSG_WriteDeltaEntity( &msg, &nullstate, base, qtrue ); + } + + MSG_WriteByte( &msg, svc_EOF ); + + MSG_WriteLong( &msg, client - svs.clients); + + // write the checksum feed + MSG_WriteLong( &msg, sv.checksumFeed); + + //rwwRMG - send info for the terrain + if ( TheRandomMissionManager ) + { + z_stream zdata; + + // Send the height map + memset(&zdata, 0, sizeof(z_stream)); + deflateInit ( &zdata, Z_MAX_COMPRESSION ); + + unsigned char heightmap[15000]; + zdata.next_out = (unsigned char*)heightmap; + zdata.avail_out = 15000; + zdata.next_in = TheRandomMissionManager->GetLandScape()->GetHeightMap(); + zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); + deflate(&zdata, Z_SYNC_FLUSH); + + MSG_WriteShort ( &msg, (unsigned short)zdata.total_out ); + MSG_WriteBits ( &msg, 1, 1 ); + MSG_WriteData ( &msg, heightmap, zdata.total_out); + + deflateEnd(&zdata); + + // Send the flatten map + memset(&zdata, 0, sizeof(z_stream)); + deflateInit ( &zdata, Z_MAX_COMPRESSION ); + + zdata.next_out = (unsigned char*)heightmap; + zdata.avail_out = 15000; + zdata.next_in = TheRandomMissionManager->GetLandScape()->GetFlattenMap(); + zdata.avail_in = TheRandomMissionManager->GetLandScape()->GetRealArea(); + deflate(&zdata, Z_SYNC_FLUSH); + + MSG_WriteShort ( &msg, (unsigned short)zdata.total_out ); + MSG_WriteBits ( &msg, 1, 1 ); + MSG_WriteData ( &msg, heightmap, zdata.total_out); + + deflateEnd(&zdata); + + // Seed is needed for misc ents and noise + MSG_WriteLong ( &msg, TheRandomMissionManager->GetLandScape()->get_rand_seed ( ) ); + + SV_WriteRMGAutomapSymbols ( &msg ); + } + else + { + MSG_WriteShort ( &msg, 0 ); + } + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + + +void SV_SendClientMapChange( client_t *client ) +{ + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // send the gamestate + MSG_WriteByte( &msg, svc_mapchange ); + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + +#ifdef _XBOX // Utilities to notify clients of various XBL state changes +/* + SV_SendClientNewPeer - tell a client to add a player to his xbOnlineInfo.xbPlayerList +*/ +void SV_SendClientNewPeer(client_t *client, XBPlayerInfo* info) +{ + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // send the command + MSG_WriteByte( &msg, svc_newpeer ); + + // We now write the specific player number as well, so the clients know where + // to put this info. (That keeps cgs.clientinfo in sync with xbPlayerList) + MSG_WriteLong( &msg, info - xbOnlineInfo.xbPlayerList ); + MSG_WriteData(&msg, info, sizeof(XBPlayerInfo)); + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + +/* + SV_SendClientRemovePeer - tell a client to remove a player from his xbOnlineInfo.xbPlayerList +*/ +void SV_SendClientRemovePeer(client_t *client, int index) +{ + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // send the command + MSG_WriteByte( &msg, svc_removepeer ); + + // All clients have IDENTICAL ordering within xbPlayerList, so just + // send the index (rather than the XUID, like we did before). + MSG_WriteLong( &msg, index ); + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} + +/* + SV_SendClientXbInfo - Sends the server's xbOnlineInfo to a given client +*/ +void SV_SendClientXbInfo(client_t *client) +{ + msg_t msg; + byte msgBuffer[MAX_MSGLEN]; + + MSG_Init( &msg, msgBuffer, sizeof( msgBuffer ) ); + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // send any server commands waiting to be sent first. + // we have to do this cause we send the client->reliableSequence + // with a gamestate and it sets the clc.serverCommandSequence at + // the client side + SV_UpdateServerCommandsToClient( client, &msg ); + + // send the command + MSG_WriteByte( &msg, svc_xbInfo ); + + MSG_WriteData(&msg, &(xbOnlineInfo), sizeof(XBOnlineInfo)); + + // deliver this to the client + SV_SendMessageToClient( &msg, client ); +} +#endif // _XBOX + +/* +================== +SV_ClientEnterWorld +================== +*/ +void SV_ClientEnterWorld( client_t *client, usercmd_t *cmd ) { + int clientNum; + sharedEntity_t *ent; + + Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); + client->state = CS_ACTIVE; + +#ifdef _XBOX + //update XbOnlineInfo with client + SV_SendClientXbInfo(client); +#endif + + // set up the entity for the client + clientNum = client - svs.clients; + ent = SV_GentityNum( clientNum ); + ent->s.number = clientNum; + client->gentity = ent; + + client->lastUserInfoChange = 0; //reset the delay + client->lastUserInfoCount = 0; //reset the count + + client->deltaMessage = -1; + client->nextSnapshotTime = svs.time; // generate a snapshot immediately + client->lastUsercmd = *cmd; + + // call the game begin function + VM_Call( gvm, GAME_CLIENT_BEGIN, client - svs.clients ); +} + +/* +============================================================ + +CLIENT COMMAND EXECUTION + +============================================================ +*/ + +/* +================== +SV_CloseDownload + +clear/free any download vars +================== +*/ +#ifndef _XBOX // No downloads on Xbox +static void SV_CloseDownload( client_t *cl ) { + int i; + + // EOF + if (cl->download) { + FS_FCloseFile( cl->download ); + } + cl->download = 0; + *cl->downloadName = 0; + + // Free the temporary buffer space + for (i = 0; i < MAX_DOWNLOAD_WINDOW; i++) { + if (cl->downloadBlocks[i]) { + Z_Free( cl->downloadBlocks[i] ); + cl->downloadBlocks[i] = NULL; + } + } + +} + +/* +================== +SV_StopDownload_f + +Abort a download if in progress +================== +*/ +void SV_StopDownload_f( client_t *cl ) { + if (*cl->downloadName) + Com_DPrintf( "clientDownload: %d : file \"%s\" aborted\n", cl - svs.clients, cl->downloadName ); + + SV_CloseDownload( cl ); +} + +/* +================== +SV_DoneDownload_f + +Downloads are finished +================== +*/ +void SV_DoneDownload_f( client_t *cl ) { + Com_DPrintf( "clientDownload: %s Done\n", cl->name); + // resend the game state to update any clients that entered during the download + SV_SendClientGameState(cl); +} + +/* +================== +SV_NextDownload_f + +The argument will be the last acknowledged block from the client, it should be +the same as cl->downloadClientBlock +================== +*/ +void SV_NextDownload_f( client_t *cl ) +{ + int block = atoi( Cmd_Argv(1) ); + + if (block == cl->downloadClientBlock) { + Com_DPrintf( "clientDownload: %d : client acknowledge of block %d\n", cl - svs.clients, block ); + + // Find out if we are done. A zero-length block indicates EOF + if (cl->downloadBlockSize[cl->downloadClientBlock % MAX_DOWNLOAD_WINDOW] == 0) { + Com_Printf( "clientDownload: %d : file \"%s\" completed\n", cl - svs.clients, cl->downloadName ); + SV_CloseDownload( cl ); + return; + } + + cl->downloadSendTime = svs.time; + cl->downloadClientBlock++; + return; + } + // We aren't getting an acknowledge for the correct block, drop the client + // FIXME: this is bad... the client will never parse the disconnect message + // because the cgame isn't loaded yet + SV_DropClient( cl, "broken download" ); +} + +/* +================== +SV_BeginDownload_f +================== +*/ +void SV_BeginDownload_f( client_t *cl ) { + + // Kill any existing download + SV_CloseDownload( cl ); + + // cl->downloadName is non-zero now, SV_WriteDownloadToClient will see this and open + // the file itself + Q_strncpyz( cl->downloadName, Cmd_Argv(1), sizeof(cl->downloadName) ); +} + +/* +================== +SV_WriteDownloadToClient + +Check to see if the client wants a file, open it if needed and start pumping the client +Fill up msg with data +================== +*/ +void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) +{ + int curindex; + int rate; + int blockspersnap; + int idPack, missionPack; + char errorMessage[1024]; + + if (!*cl->downloadName) + return; // Nothing being downloaded + + if (!cl->download) { + // We open the file here + + Com_Printf( "clientDownload: %d : begining \"%s\"\n", cl - svs.clients, cl->downloadName ); + + missionPack = FS_idPak(cl->downloadName, "missionpack"); + idPack = missionPack || FS_idPak(cl->downloadName, "base"); + + if ( !sv_allowDownload->integer || idPack || + ( cl->downloadSize = FS_SV_FOpenFileRead( cl->downloadName, &cl->download ) ) <= 0 ) { + // cannot auto-download file + if (idPack) { + Com_Printf("clientDownload: %d : \"%s\" cannot download id pk3 files\n", cl - svs.clients, cl->downloadName); + if (missionPack) { + Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload Team Arena file \"%s\"\n" + "The Team Arena mission pack can be found in your local game store.", cl->downloadName); + } + else { + Com_sprintf(errorMessage, sizeof(errorMessage), "Cannot autodownload id pk3 file \"%s\"", cl->downloadName); + } + } else if ( !sv_allowDownload->integer ) { + Com_Printf("clientDownload: %d : \"%s\" download disabled", cl - svs.clients, cl->downloadName); + if (sv_pure->integer) { + Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "You will need to get this file elsewhere before you " + "can connect to this pure server.\n", cl->downloadName); + } else { + Com_sprintf(errorMessage, sizeof(errorMessage), "Could not download \"%s\" because autodownloading is disabled on the server.\n\n" + "Set autodownload to No in your settings and you might be " + "able to connect if you do have the file.\n", cl->downloadName); + } + } else { + Com_Printf("clientDownload: %d : \"%s\" file not found on server\n", cl - svs.clients, cl->downloadName); + Com_sprintf(errorMessage, sizeof(errorMessage), "File \"%s\" not found on server for autodownloading.\n", cl->downloadName); + } + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, 0 ); // client is expecting block zero + MSG_WriteLong( msg, -1 ); // illegal file size + MSG_WriteString( msg, errorMessage ); + + *cl->downloadName = 0; + return; + } + + // Init + cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; + cl->downloadCount = 0; + cl->downloadEOF = qfalse; + } + + // Perform any reads that we need to + while (cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW && + cl->downloadSize != cl->downloadCount) { + + curindex = (cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW); + + if (!cl->downloadBlocks[curindex]) + cl->downloadBlocks[curindex] = (unsigned char *)Z_Malloc( MAX_DOWNLOAD_BLKSIZE, TAG_DOWNLOAD, qtrue ); + + cl->downloadBlockSize[curindex] = FS_Read( cl->downloadBlocks[curindex], MAX_DOWNLOAD_BLKSIZE, cl->download ); + + if (cl->downloadBlockSize[curindex] < 0) { + // EOF right now + cl->downloadCount = cl->downloadSize; + break; + } + + cl->downloadCount += cl->downloadBlockSize[curindex]; + + // Load in next block + cl->downloadCurrentBlock++; + } + + // Check to see if we have eof condition and add the EOF block + if (cl->downloadCount == cl->downloadSize && + !cl->downloadEOF && + cl->downloadCurrentBlock - cl->downloadClientBlock < MAX_DOWNLOAD_WINDOW) { + + cl->downloadBlockSize[cl->downloadCurrentBlock % MAX_DOWNLOAD_WINDOW] = 0; + cl->downloadCurrentBlock++; + + cl->downloadEOF = qtrue; // We have added the EOF block + } + + // Loop up to window size times based on how many blocks we can fit in the + // client snapMsec and rate + + // based on the rate, how many bytes can we fit in the snapMsec time of the client + // normal rate / snapshotMsec calculation + rate = cl->rate; + if ( sv_maxRate->integer ) { + if ( sv_maxRate->integer < 1000 ) { + Cvar_Set( "sv_MaxRate", "1000" ); + } + if ( sv_maxRate->integer < rate ) { + rate = sv_maxRate->integer; + } + } + + if (!rate) { + blockspersnap = 1; + } else { + blockspersnap = ( (rate * cl->snapshotMsec) / 1000 + MAX_DOWNLOAD_BLKSIZE ) / + MAX_DOWNLOAD_BLKSIZE; + } + + if (blockspersnap < 0) + blockspersnap = 1; + + while (blockspersnap--) { + + // Write out the next section of the file, if we have already reached our window, + // automatically start retransmitting + + if (cl->downloadClientBlock == cl->downloadCurrentBlock) + return; // Nothing to transmit + + if (cl->downloadXmitBlock == cl->downloadCurrentBlock) { + // We have transmitted the complete window, should we start resending? + + //FIXME: This uses a hardcoded one second timeout for lost blocks + //the timeout should be based on client rate somehow + if (svs.time - cl->downloadSendTime > 1000) + cl->downloadXmitBlock = cl->downloadClientBlock; + else + return; + } + + // Send current block + curindex = (cl->downloadXmitBlock % MAX_DOWNLOAD_WINDOW); + + MSG_WriteByte( msg, svc_download ); + MSG_WriteShort( msg, cl->downloadXmitBlock ); + + // block zero is special, contains file size + if ( cl->downloadXmitBlock == 0 ) + MSG_WriteLong( msg, cl->downloadSize ); + + MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); + + // Write the block + if ( cl->downloadBlockSize[curindex] ) { + MSG_WriteData( msg, cl->downloadBlocks[curindex], cl->downloadBlockSize[curindex] ); + } + + Com_DPrintf( "clientDownload: %d : writing block %d\n", cl - svs.clients, cl->downloadXmitBlock ); + + // Move on to the next block + // It will get sent with next snap shot. The rate will keep us in line. + cl->downloadXmitBlock++; + + cl->downloadSendTime = svs.time; + } +} +#endif // Xbox - No downloads on Xbox + +/* +================= +SV_Disconnect_f + +The client is going to disconnect, so remove the connection immediately FIXME: move to game? +================= +*/ +const char *SV_GetStringEdString(char *refSection, char *refName); +static void SV_Disconnect_f( client_t *cl ) { +// SV_DropClient( cl, "disconnected" ); + SV_DropClient( cl, SV_GetStringEdString("MP_SVGAME","DISCONNECTED") ); +} + +/* +================= +SV_VerifyPaks_f + +If we are pure, disconnect the client if they do no meet the following conditions: + +1. the first two checksums match our view of cgame and ui +2. there are no any additional checksums that we do not have + +This routine would be a bit simpler with a goto but i abstained + +================= +*/ +static void SV_VerifyPaks_f( client_t *cl ) { +#ifndef _XBOX + int nChkSum1, nChkSum2, nClientPaks, nServerPaks, i, j, nCurArg; + int nClientChkSum[1024]; + int nServerChkSum[1024]; + const char *pPaks, *pArg; + qboolean bGood = qtrue; + + // if we are pure, we "expect" the client to load certain things from + // certain pk3 files, namely we want the client to have loaded the + // ui and cgame that we think should be loaded based on the pure setting + // + if ( sv_pure->integer != 0 ) { + + bGood = qtrue; + nChkSum1 = nChkSum2 = 0; + // we run the game, so determine which cgame and ui the client "should" be running + //dlls are valid too now -rww + if (Cvar_VariableValue( "vm_cgame" )) + { + bGood = (qboolean)(FS_FileIsInPAK("vm/cgame.qvm", &nChkSum1) == 1); + } + else + { + bGood = (qboolean)(FS_FileIsInPAK("cgamex86.dll", &nChkSum1) == 1); + } + + if (bGood) + { + if (Cvar_VariableValue( "vm_ui" )) + { + bGood = (qboolean)(FS_FileIsInPAK("vm/ui.qvm", &nChkSum2) == 1); + } + else + { + bGood = (qboolean)(FS_FileIsInPAK("uix86.dll", &nChkSum2) == 1); + } + } + + nClientPaks = Cmd_Argc(); + + // start at arg 1 ( skip cl_paks ) + nCurArg = 1; + + // we basically use this while loop to avoid using 'goto' :) + while (bGood) { + + // must be at least 6: "cl_paks cgame ui @ firstref ... numChecksums" + // numChecksums is encoded + if (nClientPaks < 6) { + bGood = qfalse; + break; + } + // verify first to be the cgame checksum + pArg = Cmd_Argv(nCurArg++); + if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum1 ) { + bGood = qfalse; + break; + } + // verify the second to be the ui checksum + pArg = Cmd_Argv(nCurArg++); + if (!pArg || *pArg == '@' || atoi(pArg) != nChkSum2 ) { + bGood = qfalse; + break; + } + // should be sitting at the delimeter now + pArg = Cmd_Argv(nCurArg++); + if (*pArg != '@') { + bGood = qfalse; + break; + } + // store checksums since tokenization is not re-entrant + for (i = 0; nCurArg < nClientPaks; i++) { + nClientChkSum[i] = atoi(Cmd_Argv(nCurArg++)); + } + + // store number to compare against (minus one cause the last is the number of checksums) + nClientPaks = i - 1; + + // make sure none of the client check sums are the same + // so the client can't send 5 the same checksums + for (i = 0; i < nClientPaks; i++) { + for (j = 0; j < nClientPaks; j++) { + if (i == j) + continue; + if (nClientChkSum[i] == nClientChkSum[j]) { + bGood = qfalse; + break; + } + } + if (bGood == qfalse) + break; + } + if (bGood == qfalse) + break; + + // get the pure checksums of the pk3 files loaded by the server + pPaks = FS_LoadedPakPureChecksums(); + Cmd_TokenizeString( pPaks ); + nServerPaks = Cmd_Argc(); + if (nServerPaks > 1024) + nServerPaks = 1024; + + for (i = 0; i < nServerPaks; i++) { + nServerChkSum[i] = atoi(Cmd_Argv(i)); + } + + // check if the client has provided any pure checksums of pk3 files not loaded by the server + for (i = 0; i < nClientPaks; i++) { + for (j = 0; j < nServerPaks; j++) { + if (nClientChkSum[i] == nServerChkSum[j]) { + break; + } + } + if (j >= nServerPaks) { + bGood = qfalse; + break; + } + } + if ( bGood == qfalse ) { + break; + } + + // check if the number of checksums was correct + nChkSum1 = sv.checksumFeed; + for (i = 0; i < nClientPaks; i++) { + nChkSum1 ^= nClientChkSum[i]; + } + nChkSum1 ^= nClientPaks; + if (nChkSum1 != nClientChkSum[nClientPaks]) { + bGood = qfalse; + break; + } + + // break out + break; + } + + if (bGood) { + cl->pureAuthentic = 1; + } + else { + cl->pureAuthentic = 0; + cl->nextSnapshotTime = -1; + cl->state = CS_ACTIVE; + SV_SendClientSnapshot( cl ); + SV_DropClient( cl, "Unpure client detected. Invalid .PK3 files referenced!" ); + } + } +#endif +} + +/* +================= +SV_ResetPureClient_f +================= +*/ +static void SV_ResetPureClient_f( client_t *cl ) { + cl->pureAuthentic = 0; +} + +/* +================= +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 + if ( Sys_IsLANAddress( cl->netchan.remoteAddress ) && com_dedicated->integer != 2 ) { + cl->rate = 99999; // lans should not rate limit + } else { + val = Info_ValueForKey (cl->userinfo, "rate"); + if (strlen(val)) { + i = atoi(val); + cl->rate = i; + if (cl->rate < 1000) { + cl->rate = 1000; + } else if (cl->rate > 90000) { + cl->rate = 90000; + } + } else { + cl->rate = 3000; + } + } + val = Info_ValueForKey (cl->userinfo, "handicap"); + if (strlen(val)) { + i = atoi(val); + if (i<=0 || i>100 || strlen(val) > 4) { + Info_SetValueForKey( cl->userinfo, "handicap", "100" ); + } + } + + // 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; + } +} + +#define INFO_CHANGE_MIN_INTERVAL 6000 //6 seconds is reasonable I suppose +#define INFO_CHANGE_MAX_COUNT 3 //only allow 3 changes within the 6 seconds + +/* +================== +SV_UpdateUserinfo_f +================== +*/ +static void SV_UpdateUserinfo_f( client_t *cl ) { + Q_strncpyz( cl->userinfo, Cmd_Argv(1), sizeof(cl->userinfo) ); + +#ifdef FINAL_BUILD + if (cl->lastUserInfoChange > svs.time) + { + cl->lastUserInfoCount++; + + if (cl->lastUserInfoCount >= INFO_CHANGE_MAX_COUNT) + { + // SV_SendServerCommand(cl, "print \"Warning: Too many info changes, last info ignored\n\"\n"); + SV_SendServerCommand(cl, "print \"@@@TOO_MANY_INFO\n\"\n"); + return; + } + } + else +#endif + { + cl->lastUserInfoCount = 0; + cl->lastUserInfoChange = svs.time + INFO_CHANGE_MIN_INTERVAL; + } + + SV_UserinfoChanged( cl ); + // call prog code to allow overrides + VM_Call( gvm, GAME_CLIENT_USERINFO_CHANGED, cl - svs.clients ); +} + +typedef struct { + char *name; + void (*func)( client_t *cl ); +} ucmd_t; + +static ucmd_t ucmds[] = { + {"userinfo", SV_UpdateUserinfo_f}, + {"disconnect", SV_Disconnect_f}, + {"cp", SV_VerifyPaks_f}, + {"vdr", SV_ResetPureClient_f}, +#ifndef _XBOX // No downloads on Xbox + {"download", SV_BeginDownload_f}, + {"nextdl", SV_NextDownload_f}, + {"stopdl", SV_StopDownload_f}, + {"donedl", SV_DoneDownload_f}, +#endif + + {NULL, NULL} +}; + +/* +================== +SV_ExecuteClientCommand + +Also called by bot code +================== +*/ +void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) { + 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; + } + } + + if (clientOK) { + // pass unknown strings to the game + if (!u->name && sv.state == SS_GAME) { + VM_Call( gvm, GAME_CLIENT_COMMAND, cl - svs.clients ); + } + } +} + +/* +=============== +SV_ClientCommand +=============== +*/ +static qboolean SV_ClientCommand( client_t *cl, msg_t *msg ) { + int seq; + const char *s; + qboolean clientOk = qtrue; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed it + if ( cl->lastClientCommand >= seq ) { + return qtrue; + } + + 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_DropClient( cl, "Lost reliable commands" ); + return qfalse; + } + + // malicious users may try using too many string commands + // to lag other players. If we decide that we want to stall + // the command, we will stop processing the rest of the packet, + // including the usercmd. This causes flooders to lag themselves + // but not other people + // We don't do this when the client hasn't been active yet since its + // normal to spam a lot of commands when downloading + if ( !com_cl_running->integer && + cl->state >= CS_ACTIVE && + sv_floodProtect->integer && + svs.time < cl->nextReliableTime ) { + // ignore any other text messages from this client but let them keep playing + clientOk = qfalse; + Com_DPrintf( "client text ignored for %s\n", cl->name ); + //return qfalse; // stop processing + } + + // don't allow another command for one second + cl->nextReliableTime = svs.time + 1000; + + SV_ExecuteClientCommand( cl, s, clientOk ); + + cl->lastClientCommand = seq; + Com_sprintf(cl->lastClientCommandString, sizeof(cl->lastClientCommandString), "%s", s); + + return qtrue; // continue procesing +} + + +//================================================================================== + + +/* +================== +SV_ClientThink + +Also called by bot code +================== +*/ +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 + } + + VM_Call( gvm, GAME_CLIENT_THINK, cl - svs.clients ); +} + +/* +================== +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, qboolean delta ) { + int i, key; + int cmdCount; + usercmd_t nullcmd; + usercmd_t cmds[MAX_PACKET_USERCMDS]; + usercmd_t *cmd, *oldcmd; + + if ( delta ) { + cl->deltaMessage = cl->messageAcknowledge; + } else { + cl->deltaMessage = -1; + } + + 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; + } + + // use the checksum feed in the key + key = sv.checksumFeed; + // also use the message acknowledge + key ^= cl->messageAcknowledge; + // also use the last acknowledged server command in the key + key ^= Com_HashKey(cl->reliableCommands[ cl->reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ], 32); + + Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + for ( i = 0 ; i < cmdCount ; i++ ) { + cmd = &cmds[i]; + MSG_ReadDeltaUsercmdKey( msg, key, oldcmd, cmd ); + oldcmd = cmd; + } + + // save time for ping calculation + cl->frames[ cl->messageAcknowledge & PACKET_MASK ].messageAcked = svs.time; + + // 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] ); + // the moves can be processed normaly + } + // +#ifndef _XBOX // No pure on Xbox + if (sv_pure->integer != 0 && cl->pureAuthentic == 0) { + SV_DropClient( cl, "Cannot validate pure client!"); + return; + } +#endif + + if ( cl->state != CS_ACTIVE ) { + cl->deltaMessage = -1; + return; + } + + // usually, the first couple commands will be duplicates + // of ones we have previously received, but the servertimes + // in the commands will cause them to be immediately discarded + for ( i = 0 ; i < cmdCount ; i++ ) { + // if this is a cmd from before a map_restart ignore it + if ( cmds[i].serverTime > cmds[cmdCount-1].serverTime ) { + continue; + } + // extremely lagged or cmd from before a map_restart + //if ( cmds[i].serverTime > svs.time + 3000 ) { + // continue; + //} + // don't execute if this is an old cmd which is already executed + // these old cmds are included when cl_packetdup > 0 + if ( cmds[i].serverTime <= cl->lastUsercmd.serverTime ) { + continue; + } + SV_ClientThink (cl, &cmds[ i ]); + } +} + + +/* +=========================================================================== + +USER CMD EXECUTION + +=========================================================================== +*/ + +/* +=================== +SV_ExecuteClientMessage + +Parse a client packet +=================== +*/ +void SV_ExecuteClientMessage( client_t *cl, msg_t *msg ) { + int c; + int serverId; + + MSG_Bitstream(msg); + + serverId = MSG_ReadLong( msg ); + cl->messageAcknowledge = MSG_ReadLong( msg ); + + if (cl->messageAcknowledge < 0) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging + //SV_DropClient( cl, "illegible client message" ); + return; + } + + cl->reliableAcknowledge = MSG_ReadLong( msg ); + + // NOTE: when the client message is fux0red the acknowledgement numbers + // can be out of range, this could cause the server to send thousands of server + // commands which the server thinks are not yet acknowledged in SV_UpdateServerCommandsToClient + if (cl->reliableAcknowledge < cl->reliableSequence - MAX_RELIABLE_COMMANDS) { + // usually only hackers create messages like this + // it is more annoying for them to let them hanging + //SV_DropClient( cl, "illegible client message" ); + cl->reliableAcknowledge = cl->reliableSequence; + return; + } + // if this is a usercmd from a previous gamestate, + // ignore it or retransmit the current gamestate + // + // if the client was downloading, let it stay at whatever serverId and + // gamestate it was at. This allows it to keep downloading even when + // the gamestate changes. After the download is finished, we'll + // notice and send it a new game state +#ifdef _XBOX // No downloads on Xbox + if ( serverId != sv.serverId ) { +#else + if ( serverId != sv.serverId && !*cl->downloadName ) { +#endif + if ( serverId == sv.restartedServerId ) { + // they just haven't caught the map_restart yet + return; + } + // if we can tell that the client has dropped the last + // gamestate we sent them, resend it + if ( cl->messageAcknowledge > cl->gamestateMessageNum ) { + Com_DPrintf( "%s : dropped gamestate, resending\n", cl->name ); + SV_SendClientGameState( cl ); + } + return; + } + + // read optional clientCommand strings + do { + c = MSG_ReadByte( msg ); + if ( c == clc_EOF ) { + break; + } + if ( c != clc_clientCommand ) { + break; + } + if ( !SV_ClientCommand( cl, msg ) ) { + return; // we couldn't execute it because of the flood protection + } + if (cl->state == CS_ZOMBIE) { + return; // disconnect command + } + } while ( 1 ); + + // read the usercmd_t + if ( c == clc_move ) { + SV_UserMove( cl, msg, qtrue ); + } else if ( c == clc_moveNoDelta ) { + SV_UserMove( cl, msg, qfalse ); + } else if ( c != clc_EOF ) { + Com_Printf( "WARNING: bad command byte for client %i\n", cl - svs.clients ); + } +// if ( msg->readcount != msg->cursize ) { +// Com_Printf( "WARNING: Junk at end of packet for client %i\n", cl - svs.clients ); +// } +} + diff --git a/codemp/server/sv_game.cpp b/codemp/server/sv_game.cpp new file mode 100644 index 0000000..28af282 --- /dev/null +++ b/codemp/server/sv_game.cpp @@ -0,0 +1,1758 @@ +// sv_game.c -- interface to the game dll +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "server.h" + +#include "../game/botlib.h" +#include "../qcommon/stringed_ingame.h" + +#if !defined(CROFFSYSTEM_H_INC) + #include "../qcommon/RoffSystem.h" +#endif + +#include "../renderer/tr_WorldEffects.h" + +#if !defined(G2_H_INC) + #include "../ghoul2/G2_local.h" +#endif + +#include "../ghoul2/G2_gore.h" + +#include "../RMG/RM_Headers.h" +#include "../qcommon/cm_local.h" +#include "../qcommon/cm_public.h" + +#include "../icarus/GameInterface.h" + +#include "../qcommon/timing.h" + +#include "NPCNav/navigator.h" + +botlib_export_t *botlib_export; + +extern CMiniHeap *G2VertSpaceServer; + +void SV_GameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + +void SV_GamePrint( const char *string ) { + Com_Printf( "%s", string ); +} + +// 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( sharedEntity_t *ent ) { + int num; + + num = ( (byte *)ent - (byte *)sv.gentities ) / sv.gentitySize; + + return num; +} + +sharedEntity_t *SV_GentityNum( int num ) { + sharedEntity_t *ent; + + ent = (sharedEntity_t *)((byte *)sv.gentities + sv.gentitySize*(num)); + + return ent; +} + +playerState_t *SV_GameClientNum( int num ) { + playerState_t *ps; + + ps = (playerState_t *)((byte *)sv.gameClients + sv.gameClientSize*(num)); + + return ps; +} + +svEntity_t *SV_SvEntityForGentity( sharedEntity_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 ]; +} + +sharedEntity_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 *text ) { + if ( clientNum == -1 ) { + SV_SendServerCommand( NULL, "%s", text ); + } else { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_SendServerCommand( svs.clients + clientNum, "%s", text ); + } +} + + +/* +=============== +SV_GameDropClient + +Disconnects the client with a message +=============== +*/ +void SV_GameDropClient( int clientNum, const char *reason ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + return; + } + SV_DropClient( svs.clients + clientNum, reason ); +} + +int CM_ModelContents( clipHandle_t model, int subBSPIndex ); +int CM_LoadSubBSP(const char *name, qboolean clientload); +int CM_FindSubBSP(int modelIndex); +char *CM_SubBSPEntityString( int index ); +/* +================= +SV_SetBrushModel + +sets mins and maxs for inline bmodels +================= +*/ +void SV_SetBrushModel( sharedEntity_t *ent, const char *name ) +{ + clipHandle_t h; + vec3_t mins, maxs; + + if (!name) + { + Com_Error( ERR_DROP, "SV_SetBrushModel: NULL" ); + } + + if (name[0] == '*') + { + ent->s.modelindex = atoi( name + 1 ); + + if (sv.mLocalSubBSPIndex != -1) + { + ent->s.modelindex += sv.mLocalSubBSPModelOffset; + } + + h = CM_InlineModel( ent->s.modelindex ); + + CM_ModelBounds(h, mins, maxs); + + VectorCopy (mins, ent->r.mins); + VectorCopy (maxs, ent->r.maxs); + ent->r.bmodel = qtrue; + + if (com_RMG && com_RMG->integer) + { + ent->r.contents = CM_ModelContents( h, sv.mLocalSubBSPIndex ); + } + else + { + ent->r.contents = CM_ModelContents( h, -1 ); + } + } + else if (name[0] == '#') + { + ent->s.modelindex = CM_LoadSubBSP(va("maps/%s.bsp", name + 1), qfalse); + CM_ModelBounds( ent->s.modelindex, mins, maxs ); + + VectorCopy (mins, ent->r.mins); + VectorCopy (maxs, ent->r.maxs); + ent->r.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->r.contents = CM_ModelContents( h, CM_FindSubBSP(ent->s.modelindex) ); + } + else + { + Com_Error( ERR_DROP, "SV_SetBrushModel: %s isn't a brush model", name ); + } +} + +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 + + 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)) ) ) ) + return qfalse; + if (!CM_AreasConnected (area1, area2)) + return qfalse; // a door blocks sight + 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 + + 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)) ) ) ) + return qfalse; + + return qtrue; +} + + +/* +======================== +SV_AdjustAreaPortalState +======================== +*/ +void SV_AdjustAreaPortalState( sharedEntity_t *ent, qboolean open ) { + 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 sharedEntity_t *gEnt, int capsule ) { + const float *origin, *angles; + clipHandle_t ch; + trace_t trace; + + // check for exact collision + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + ch = SV_ClipHandleForEntity( gEnt ); + CM_TransformedBoxTrace ( &trace, vec3_origin, vec3_origin, mins, maxs, + ch, -1, origin, angles, capsule ); + + return (qboolean)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 ); +} + +/* +=============== +SV_LocateGameData + +=============== +*/ +void SV_LocateGameData( sharedEntity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *clients, int sizeofGameClient ) { + sv.gentities = gEnts; + sv.gentitySize = sizeofGEntity_t; + sv.num_entities = numGEntities; + + sv.gameClients = clients; + sv.gameClientSize = sizeofGameClient; +} + +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_GetUsercmd + +=============== +*/ +void SV_GetUsercmd( int clientNum, usercmd_t *cmd ) { + if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { + Com_Error( ERR_DROP, "SV_GetUsercmd: bad clientNum:%i", clientNum ); + } + *cmd = svs.clients[clientNum].lastUsercmd; +} + +//============================================== + +static int FloatAsInt( float f ) { + int temp; + + *(float *)&temp = f; + + return temp; +} + +/* +==================== +SV_GameSystemCalls + +The module is making a system call +==================== +*/ +//rcg010207 - see my comments in VM_DllSyscall(), in qcommon/vm.c ... +#if ((defined __linux__) && (defined __powerpc__)) +#define VMA(x) ((void *) args[x]) +#else +#define VMA(x) VM_ArgPtr(args[x]) +#endif + +#define VMF(x) ((float *)args)[x] + +void SV_BotWaypointReception(int wpnum, wpobject_t **wps); +void SV_BotCalculatePaths(int rmg); + +qboolean Q3_TaskIDPending( sharedEntity_t *ent, taskID_t taskType ); +void Q3_TaskIDSet( sharedEntity_t *ent, taskID_t taskType, int taskID ); +void Q3_TaskIDComplete( sharedEntity_t *ent, taskID_t taskType ); +void Q3_SetVar( int taskID, int entID, const char *type_name, const char *data ); +int Q3_VariableDeclared( const char *name ); +int Q3_GetFloatVariable( const char *name, float *value ); +int Q3_GetStringVariable( const char *name, const char **value ); +int Q3_GetVectorVariable( const char *name, vec3_t value ); + +sharedEntity_t gLocalModifier; + +sharedEntity_t *ConvertedEntity(sharedEntity_t *ent) +{ //Return an entity with the memory shifted around to allow reading/modifying VM memory + int i = 0; + + assert(ent); + + gLocalModifier.s = ent->s; + gLocalModifier.r = ent->r; + while (i < NUM_TIDS) + { + gLocalModifier.taskID[i] = ent->taskID[i]; + i++; + } + i = 0; + gLocalModifier.parms = (parms_t *)VM_ArgPtr((int)ent->parms); + while (i < NUM_BSETS) + { + gLocalModifier.behaviorSet[i] = (char *)VM_ArgPtr((int)ent->behaviorSet[i]); + i++; + } + i = 0; + gLocalModifier.script_targetname = (char *)VM_ArgPtr((int)ent->script_targetname); + gLocalModifier.delayScriptTime = ent->delayScriptTime; + gLocalModifier.fullName = (char *)VM_ArgPtr((int)ent->fullName); + gLocalModifier.targetname = (char *)VM_ArgPtr((int)ent->targetname); + gLocalModifier.classname = (char *)VM_ArgPtr((int)ent->classname); + + gLocalModifier.ghoul2 = ent->ghoul2; + + return &gLocalModifier; +} + +siegePers_t sv_siegePersData = {qfalse, 0, 0}; + +qhandle_t RE_RegisterServerSkin( const char *name ); +extern float g_svCullDist; +int SV_GameSystemCalls( 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 G_PRINT: + Com_Printf( "%s", VMA(1) ); + return 0; + case G_ERROR: + Com_Error( ERR_DROP, "%s", VMA(1) ); + return 0; + case G_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 G_CVAR_REGISTER: + Cvar_Register( (vmCvar_t *)VMA(1), (const char *)VMA(2), (const char *)VMA(3), args[4] ); + return 0; + case G_CVAR_UPDATE: + Cvar_Update( (vmCvar_t *)VMA(1) ); + return 0; + case G_CVAR_SET: + Cvar_Set( (const char *)VMA(1), (const char *)VMA(2) ); + return 0; + case G_CVAR_VARIABLE_INTEGER_VALUE: + return Cvar_VariableIntegerValue( (const char *)VMA(1) ); + case G_CVAR_VARIABLE_STRING_BUFFER: + Cvar_VariableStringBuffer( (const char *)VMA(1), (char *)VMA(2), args[3] ); + return 0; + case G_ARGC: + return Cmd_Argc(); + case G_ARGV: + Cmd_ArgvBuffer( args[1], (char *)VMA(2), args[3] ); + return 0; + case G_SEND_CONSOLE_COMMAND: + Cbuf_ExecuteText( args[1], (const char *)VMA(2) ); + return 0; + + case G_FS_FOPEN_FILE: + return FS_FOpenFileByMode( (const char *)VMA(1), (int *)VMA(2), (fsMode_t)args[3] ); + case G_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + case G_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case G_FS_FCLOSE_FILE: + FS_FCloseFile( args[1] ); + return 0; + case G_FS_GETFILELIST: + return FS_GetFileList( (const char *)VMA(1), (const char *)VMA(2), (char *)VMA(3), args[4] ); + + case G_LOCATE_GAME_DATA: + SV_LocateGameData( (sharedEntity_t *)VMA(1), args[2], args[3], (struct playerState_s *)VMA(4), args[5] ); + return 0; + case G_DROP_CLIENT: + SV_GameDropClient( args[1], (const char *)VMA(2) ); + return 0; + case G_SEND_SERVER_COMMAND: + SV_GameSendServerCommand( args[1], (const char *)VMA(2) ); + return 0; + case G_LINKENTITY: + SV_LinkEntity( (sharedEntity_t *)VMA(1) ); + return 0; + case G_UNLINKENTITY: + SV_UnlinkEntity( (sharedEntity_t *)VMA(1) ); + return 0; + case G_ENTITIES_IN_BOX: + return SV_AreaEntities( (const float *)VMA(1), (const float *)VMA(2), (int *)VMA(3), args[4] ); + case G_ENTITY_CONTACT: + return SV_EntityContact( (const float *)VMA(1), (const float *)VMA(2), (const sharedEntity_t *)VMA(3), /*int capsule*/ qfalse ); + case G_ENTITY_CONTACTCAPSULE: + return SV_EntityContact( (const float *)VMA(1), (const float *)VMA(2), (const sharedEntity_t *)VMA(3), /*int capsule*/ qtrue ); + case G_TRACE: + SV_Trace( (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, /*args[8]*/0, args[9] ); + return 0; + case G_G2TRACE: + SV_Trace( (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, args[8], args[9] ); + return 0; + case G_TRACECAPSULE: + SV_Trace( (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, args[8], args[9] ); + return 0; + case G_POINT_CONTENTS: + return SV_PointContents( (const float *)VMA(1), args[2] ); + case G_SET_SERVER_CULL: + g_svCullDist = VMF(1); + return 0; + case G_SET_BRUSH_MODEL: + SV_SetBrushModel( (sharedEntity_t *)VMA(1), (const char *)VMA(2) ); + return 0; + case G_IN_PVS: + return SV_inPVS( (const float *)VMA(1), (const float *)VMA(2) ); + case G_IN_PVS_IGNORE_PORTALS: + return SV_inPVSIgnorePortals( (const float *)VMA(1), (const float *)VMA(2) ); + + case G_SET_CONFIGSTRING: + SV_SetConfigstring( args[1], (const char *)VMA(2) ); + return 0; + case G_GET_CONFIGSTRING: + SV_GetConfigstring( args[1], (char *)VMA(2), args[3] ); + return 0; + case G_SET_USERINFO: + SV_SetUserinfo( args[1], (const char *)VMA(2) ); + return 0; + case G_GET_USERINFO: + SV_GetUserinfo( args[1], (char *)VMA(2), args[3] ); + return 0; + case G_GET_SERVERINFO: + SV_GetServerinfo( (char *)VMA(1), args[2] ); + return 0; + case G_ADJUST_AREA_PORTAL_STATE: + SV_AdjustAreaPortalState( (sharedEntity_t *)VMA(1), (qboolean)args[2] ); + return 0; + case G_AREAS_CONNECTED: + return CM_AreasConnected( args[1], args[2] ); + + case G_BOT_ALLOCATE_CLIENT: + return SV_BotAllocateClient(); + case G_BOT_FREE_CLIENT: + SV_BotFreeClient( args[1] ); + return 0; + + case G_GET_USERCMD: + SV_GetUsercmd( args[1], (struct usercmd_s *)VMA(2) ); + return 0; + + case G_SIEGEPERSSET: + sv_siegePersData = *((siegePers_t *)VMA(1)); + return 0; + + case G_SIEGEPERSGET: + *((siegePers_t *)VMA(1)) = sv_siegePersData; + return 0; + + //rwwRMG - see below + /* + case G_GET_ENTITY_TOKEN: + { + const char *s; + + s = COM_Parse( (const char **) &sv.entityParsePoint ); + Q_strncpyz( (char *)VMA(1), s, args[2] ); + if ( !sv.entityParsePoint && !s[0] ) { + return qfalse; + } else { + return qtrue; + } + } + */ + + /* + case G_BOT_GET_MEMORY: + void *ptr; + ptr = Bot_GetMemoryGame(args[1]); + return (int)ptr; + case G_BOT_FREE_MEMORY: + Bot_FreeMemoryGame((void *)VMA(1)); + return 0; + */ + case G_DEBUG_POLYGON_CREATE: + return BotImport_DebugPolygonCreate( args[1], args[2], (float (*)[3])VMA(3) ); + case G_DEBUG_POLYGON_DELETE: + BotImport_DebugPolygonDelete( args[1] ); + return 0; + case G_REAL_TIME: + return Com_RealTime( (struct qtime_s *)VMA(1) ); + case G_SNAPVECTOR: + Sys_SnapVector( (float *)VMA(1) ); + return 0; + +// case SP_REGISTER_SERVER_CMD: +// return SP_RegisterServer( (const char *)VMA(1) ); + case SP_GETSTRINGTEXTSTRING: + //return (int)SP_GetStringTextString((char *)VMA(1)); + 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 + { + Q_strncpyz( (char *) VMA(2), "??", args[3] ); + return qfalse; + } + break; + + case G_ROFF_CLEAN: + return theROFFSystem.Clean(qfalse); + + case G_ROFF_UPDATE_ENTITIES: + theROFFSystem.UpdateEntities(qfalse); + return 0; + + case G_ROFF_CACHE: + return theROFFSystem.Cache( (char *)VMA(1), qfalse ); + + case G_ROFF_PLAY: + return theROFFSystem.Play(args[1], args[2], (qboolean)args[3], qfalse ); + + case G_ROFF_PURGE_ENT: + return theROFFSystem.PurgeEnt( args[1], qfalse ); + + //rww - dynamic vm memory allocation! + case G_TRUEMALLOC: + VM_Shifted_Alloc((void **)VMA(1), args[2]); + return 0; + case G_TRUEFREE: + VM_Shifted_Free((void **)VMA(1)); + return 0; + + //rww - icarus traps + case G_ICARUS_RUNSCRIPT: + return ICARUS_RunScript(ConvertedEntity((sharedEntity_t *)VMA(1)), (const char *)VMA(2)); + + case G_ICARUS_REGISTERSCRIPT: + return ICARUS_RegisterScript((const char *)VMA(1), (qboolean)args[2]); + + case G_ICARUS_INIT: + ICARUS_Init(); + return 0; + + case G_ICARUS_VALIDENT: + return ICARUS_ValidEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); + + case G_ICARUS_ISINITIALIZED: + { + int entID = args[1]; + + if (!gSequencers[entID] || !gTaskManagers[entID]) + { + return 0; + } + } + return 1; + + case G_ICARUS_MAINTAINTASKMANAGER: + { + int entID = args[1]; + + if (gTaskManagers[entID]) + { + gTaskManagers[entID]->Update(); + return 1; + } + } + return 0; + case G_ICARUS_ISRUNNING: + { + int entID = args[1]; + + if (!gTaskManagers[entID] || !gTaskManagers[entID]->IsRunning()) + { + return 0; + } + } + return 1; + + case G_ICARUS_TASKIDPENDING: + return Q3_TaskIDPending((sharedEntity_t *)VMA(1), (taskID_t)args[2]); + + case G_ICARUS_INITENT: + ICARUS_InitEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); + return 0; + + case G_ICARUS_FREEENT: + ICARUS_FreeEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); + return 0; + + case G_ICARUS_ASSOCIATEENT: + ICARUS_AssociateEnt(ConvertedEntity((sharedEntity_t *)VMA(1))); + return 0; + + case G_ICARUS_SHUTDOWN: + ICARUS_Shutdown(); + return 0; + + case G_ICARUS_TASKIDSET: + //rww - note that we are passing in the true entity here. + //This is because we allow modification of certain non-pointer values, + //which is valid. + Q3_TaskIDSet((sharedEntity_t *)VMA(1), (taskID_t)args[2], args[3]); + return 0; + + case G_ICARUS_TASKIDCOMPLETE: + //same as above. + Q3_TaskIDComplete((sharedEntity_t *)VMA(1), (taskID_t)args[2]); + return 0; + + case G_ICARUS_SETVAR: + Q3_SetVar(args[1], args[2], (const char *)VMA(3), (const char *)VMA(4)); + return 0; + + case G_ICARUS_VARIABLEDECLARED: + return Q3_VariableDeclared((const char *)VMA(1)); + + case G_ICARUS_GETFLOATVARIABLE: + return Q3_GetFloatVariable((const char *)VMA(1), (float *)VMA(2)); + + case G_ICARUS_GETSTRINGVARIABLE: + { + const char *rec = (const char *)VMA(2); + return Q3_GetStringVariable((const char *)VMA(1), (const char **)&rec); + } + + case G_ICARUS_GETVECTORVARIABLE: + return Q3_GetVectorVariable((const char *)VMA(1), (float *)VMA(2)); + + + //rww - BEGIN NPC NAV TRAPS + case G_NAV_INIT: + navigator.Init(); + return 0; + case G_NAV_FREE: + navigator.Free(); + return 0; + case G_NAV_LOAD: + return navigator.Load((const char *)VMA(1), args[2]); + case G_NAV_SAVE: + return navigator.Save((const char *)VMA(1), args[2]); + case G_NAV_ADDRAWPOINT: + return navigator.AddRawPoint((float *)VMA(1), args[2], args[3]); + case G_NAV_CALCULATEPATHS: + navigator.CalculatePaths((qboolean)args[1]); + return 0; + case G_NAV_HARDCONNECT: + navigator.HardConnect(args[1], args[2]); + return 0; + case G_NAV_SHOWNODES: + navigator.ShowNodes(); + return 0; + case G_NAV_SHOWEDGES: + navigator.ShowEdges(); + return 0; + case G_NAV_SHOWPATH: + navigator.ShowPath(args[1], args[2]); + return 0; + case G_NAV_GETNEARESTNODE: + return navigator.GetNearestNode((sharedEntity_t *)VMA(1), args[2], args[3], args[4]); + case G_NAV_GETBESTNODE: + return navigator.GetBestNode(args[1], args[2], args[3]); + case G_NAV_GETNODEPOSITION: + return navigator.GetNodePosition(args[1], (float *)VMA(2)); + case G_NAV_GETNODENUMEDGES: + return navigator.GetNodeNumEdges(args[1]); + case G_NAV_GETNODEEDGE: + return navigator.GetNodeEdge(args[1], args[2]); + case G_NAV_GETNUMNODES: + return navigator.GetNumNodes(); + case G_NAV_CONNECTED: + return navigator.Connected(args[1], args[2]); + case G_NAV_GETPATHCOST: + return navigator.GetPathCost(args[1], args[2]); + case G_NAV_GETEDGECOST: + return navigator.GetEdgeCost(args[1], args[2]); + case G_NAV_GETPROJECTEDNODE: + return navigator.GetProjectedNode((float *)VMA(1), args[2]); + case G_NAV_CHECKFAILEDNODES: + navigator.CheckFailedNodes((sharedEntity_t *)VMA(1)); + return 0; + case G_NAV_ADDFAILEDNODE: + navigator.AddFailedNode((sharedEntity_t *)VMA(1), args[2]); + return 0; + case G_NAV_NODEFAILED: + return navigator.NodeFailed((sharedEntity_t *)VMA(1), args[2]); + case G_NAV_NODESARENEIGHBORS: + return navigator.NodesAreNeighbors(args[1], args[2]); + case G_NAV_CLEARFAILEDEDGE: + navigator.ClearFailedEdge((failedEdge_t *)VMA(1)); + return 0; + case G_NAV_CLEARALLFAILEDEDGES: + navigator.ClearAllFailedEdges(); + return 0; + case G_NAV_EDGEFAILED: + return navigator.EdgeFailed(args[1], args[2]); + case G_NAV_ADDFAILEDEDGE: + navigator.AddFailedEdge(args[1], args[2], args[3]); + return 0; + case G_NAV_CHECKFAILEDEDGE: + return navigator.CheckFailedEdge((failedEdge_t *)VMA(1)); + case G_NAV_CHECKALLFAILEDEDGES: + navigator.CheckAllFailedEdges(); + return 0; + case G_NAV_ROUTEBLOCKED: + return navigator.RouteBlocked(args[1], args[2], args[3], args[4]); + case G_NAV_GETBESTNODEALTROUTE: + return navigator.GetBestNodeAltRoute(args[1], args[2], (int *)VMA(3), args[4]); + case G_NAV_GETBESTNODEALT2: + return navigator.GetBestNodeAltRoute(args[1], args[2], args[3]); + case G_NAV_GETBESTPATHBETWEENENTS: + return navigator.GetBestPathBetweenEnts((sharedEntity_t *)VMA(1), (sharedEntity_t *)VMA(2), args[3]); + case G_NAV_GETNODERADIUS: + return navigator.GetNodeRadius(args[1]); + case G_NAV_CHECKBLOCKEDEDGES: + navigator.CheckBlockedEdges(); + return 0; + case G_NAV_CLEARCHECKEDNODES: + navigator.ClearCheckedNodes(); + return 0; + case G_NAV_CHECKEDNODE: + return navigator.CheckedNode(args[1], args[2]); + case G_NAV_SETCHECKEDNODE: + navigator.SetCheckedNode(args[1], args[2], args[3]); + case G_NAV_FLAGALLNODES: + navigator.FlagAllNodes(args[1]); + case G_NAV_GETPATHSCALCULATED: + return navigator.pathsCalculated; + case G_NAV_SETPATHSCALCULATED: + navigator.pathsCalculated = (qboolean)args[1]; + return 0; + //rww - END NPC NAV TRAPS + + case G_SET_SHARED_BUFFER: + sv.mSharedMemory = ((char *)VMA(1)); + return 0; + //==================================== + + case BOTLIB_SETUP: + return SV_BotLibSetup(); + case BOTLIB_SHUTDOWN: + return SV_BotLibShutdown(); + case BOTLIB_LIBVAR_SET: + return botlib_export->BotLibVarSet( (char *)VMA(1), (char *)VMA(2) ); + case BOTLIB_LIBVAR_GET: + return botlib_export->BotLibVarGet( (char *)VMA(1), (char *)VMA(2), args[3] ); + + case BOTLIB_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( (char *)VMA(1) ); + case BOTLIB_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( (const char *)VMA(1) ); + case BOTLIB_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case BOTLIB_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], (struct pc_token_s *)VMA(2) ); + case BOTLIB_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], (char *)VMA(2), (int *)VMA(3) ); + + case BOTLIB_START_FRAME: + return botlib_export->BotLibStartFrame( VMF(1) ); + case BOTLIB_LOAD_MAP: + return botlib_export->BotLibLoadMap( (const char *)VMA(1) ); + case BOTLIB_UPDATENTITY: + return botlib_export->BotLibUpdateEntity( args[1], (struct bot_entitystate_s *)VMA(2) ); + case BOTLIB_TEST: + return botlib_export->Test( args[1], (char *)VMA(2), (float *)VMA(3), (float *)VMA(4) ); + + case BOTLIB_GET_SNAPSHOT_ENTITY: + return SV_BotGetSnapshotEntity( args[1], args[2] ); + case BOTLIB_GET_CONSOLE_MESSAGE: + return SV_BotGetConsoleMessage( args[1], (char *)VMA(2), args[3] ); + case BOTLIB_USER_COMMAND: + SV_ClientThink( &svs.clients[args[1]], (struct usercmd_s *)VMA(2) ); + return 0; + + case BOTLIB_AAS_BBOX_AREAS: + return botlib_export->aas.AAS_BBoxAreas( (float *)VMA(1), (float *)VMA(2), (int *)VMA(3), args[4] ); + case BOTLIB_AAS_AREA_INFO: + return botlib_export->aas.AAS_AreaInfo( args[1], (struct aas_areainfo_s *)VMA(2) ); + case BOTLIB_AAS_ALTERNATIVE_ROUTE_GOAL: + return botlib_export->aas.AAS_AlternativeRouteGoals( (float *)VMA(1), args[2], (float *)VMA(3), args[4], args[5], (struct aas_altroutegoal_s *)VMA(6), args[7], args[8] ); + case BOTLIB_AAS_ENTITY_INFO: + botlib_export->aas.AAS_EntityInfo( args[1], (struct aas_entityinfo_s *)VMA(2) ); + return 0; + + case BOTLIB_AAS_INITIALIZED: + return botlib_export->aas.AAS_Initialized(); + case BOTLIB_AAS_PRESENCE_TYPE_BOUNDING_BOX: + botlib_export->aas.AAS_PresenceTypeBoundingBox( args[1], (float *)VMA(2), (float *)VMA(3) ); + return 0; + case BOTLIB_AAS_TIME: + return FloatAsInt( botlib_export->aas.AAS_Time() ); + + case BOTLIB_AAS_POINT_AREA_NUM: + return botlib_export->aas.AAS_PointAreaNum( (float *)VMA(1) ); + case BOTLIB_AAS_POINT_REACHABILITY_AREA_INDEX: + return botlib_export->aas.AAS_PointReachabilityAreaIndex( (float *)VMA(1) ); + case BOTLIB_AAS_TRACE_AREAS: + return botlib_export->aas.AAS_TraceAreas( (float *)VMA(1), (float *)VMA(2), (int *)VMA(3), (float (*)[3])VMA(4), args[5] ); + + case BOTLIB_AAS_POINT_CONTENTS: + return botlib_export->aas.AAS_PointContents( (float *)VMA(1) ); + case BOTLIB_AAS_NEXT_BSP_ENTITY: + return botlib_export->aas.AAS_NextBSPEntity( args[1] ); + case BOTLIB_AAS_VALUE_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_ValueForBSPEpairKey( args[1], (char *)VMA(2), (char *)VMA(3), args[4] ); + case BOTLIB_AAS_VECTOR_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_VectorForBSPEpairKey( args[1], (char *)VMA(2), (float *)VMA(3) ); + case BOTLIB_AAS_FLOAT_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_FloatForBSPEpairKey( args[1], (char *)VMA(2), (float *)VMA(3) ); + case BOTLIB_AAS_INT_FOR_BSP_EPAIR_KEY: + return botlib_export->aas.AAS_IntForBSPEpairKey( args[1], (char *)VMA(2), (int *)VMA(3) ); + + case BOTLIB_AAS_AREA_REACHABILITY: + return botlib_export->aas.AAS_AreaReachability( args[1] ); + + case BOTLIB_AAS_AREA_TRAVEL_TIME_TO_GOAL_AREA: + return botlib_export->aas.AAS_AreaTravelTimeToGoalArea( args[1], (float *)VMA(2), args[3], args[4] ); + case BOTLIB_AAS_ENABLE_ROUTING_AREA: + return botlib_export->aas.AAS_EnableRoutingArea( args[1], args[2] ); + case BOTLIB_AAS_PREDICT_ROUTE: + return botlib_export->aas.AAS_PredictRoute( (struct aas_predictroute_s *)VMA(1), args[2], (float *)VMA(3), args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11] ); + + case BOTLIB_AAS_SWIMMING: + return botlib_export->aas.AAS_Swimming( (float *)VMA(1) ); + case BOTLIB_AAS_PREDICT_CLIENT_MOVEMENT: + return botlib_export->aas.AAS_PredictClientMovement( (struct aas_clientmove_s *)VMA(1), args[2], (float *)VMA(3), args[4], args[5], + (float *)VMA(6), (float *)VMA(7), args[8], args[9], VMF(10), args[11], args[12], args[13] ); + + case BOTLIB_EA_SAY: + botlib_export->ea.EA_Say( args[1], (char *)VMA(2) ); + return 0; + case BOTLIB_EA_SAY_TEAM: + botlib_export->ea.EA_SayTeam( args[1], (char *)VMA(2) ); + return 0; + case BOTLIB_EA_COMMAND: + botlib_export->ea.EA_Command( args[1], (char *)VMA(2) ); + return 0; + + case BOTLIB_EA_ACTION: + botlib_export->ea.EA_Action( args[1], args[2] ); + break; + case BOTLIB_EA_GESTURE: + botlib_export->ea.EA_Gesture( args[1] ); + return 0; + case BOTLIB_EA_TALK: + botlib_export->ea.EA_Talk( args[1] ); + return 0; + case BOTLIB_EA_ATTACK: + botlib_export->ea.EA_Attack( args[1] ); + return 0; + case BOTLIB_EA_ALT_ATTACK: + botlib_export->ea.EA_Alt_Attack( args[1] ); + return 0; + case BOTLIB_EA_FORCEPOWER: + botlib_export->ea.EA_ForcePower( args[1] ); + return 0; + case BOTLIB_EA_USE: + botlib_export->ea.EA_Use( args[1] ); + return 0; + case BOTLIB_EA_RESPAWN: + botlib_export->ea.EA_Respawn( args[1] ); + return 0; + case BOTLIB_EA_CROUCH: + botlib_export->ea.EA_Crouch( args[1] ); + return 0; + case BOTLIB_EA_MOVE_UP: + botlib_export->ea.EA_MoveUp( args[1] ); + return 0; + case BOTLIB_EA_MOVE_DOWN: + botlib_export->ea.EA_MoveDown( args[1] ); + return 0; + case BOTLIB_EA_MOVE_FORWARD: + botlib_export->ea.EA_MoveForward( args[1] ); + return 0; + case BOTLIB_EA_MOVE_BACK: + botlib_export->ea.EA_MoveBack( args[1] ); + return 0; + case BOTLIB_EA_MOVE_LEFT: + botlib_export->ea.EA_MoveLeft( args[1] ); + return 0; + case BOTLIB_EA_MOVE_RIGHT: + botlib_export->ea.EA_MoveRight( args[1] ); + return 0; + + case BOTLIB_EA_SELECT_WEAPON: + botlib_export->ea.EA_SelectWeapon( args[1], args[2] ); + return 0; + case BOTLIB_EA_JUMP: + botlib_export->ea.EA_Jump( args[1] ); + return 0; + case BOTLIB_EA_DELAYED_JUMP: + botlib_export->ea.EA_DelayedJump( args[1] ); + return 0; + case BOTLIB_EA_MOVE: + botlib_export->ea.EA_Move( args[1], (float *)VMA(2), VMF(3) ); + return 0; + case BOTLIB_EA_VIEW: + botlib_export->ea.EA_View( args[1], (float *)VMA(2) ); + return 0; + + case BOTLIB_EA_END_REGULAR: + botlib_export->ea.EA_EndRegular( args[1], VMF(2) ); + return 0; + case BOTLIB_EA_GET_INPUT: + botlib_export->ea.EA_GetInput( args[1], VMF(2), (struct bot_input_s *)VMA(3) ); + return 0; + case BOTLIB_EA_RESET_INPUT: + botlib_export->ea.EA_ResetInput( args[1] ); + return 0; + + case BOTLIB_AI_LOAD_CHARACTER: + return botlib_export->ai.BotLoadCharacter( (char *)VMA(1), VMF(2) ); + case BOTLIB_AI_FREE_CHARACTER: + botlib_export->ai.BotFreeCharacter( args[1] ); + return 0; + case BOTLIB_AI_CHARACTERISTIC_FLOAT: + return FloatAsInt( botlib_export->ai.Characteristic_Float( args[1], args[2] ) ); + case BOTLIB_AI_CHARACTERISTIC_BFLOAT: + return FloatAsInt( botlib_export->ai.Characteristic_BFloat( args[1], args[2], VMF(3), VMF(4) ) ); + case BOTLIB_AI_CHARACTERISTIC_INTEGER: + return botlib_export->ai.Characteristic_Integer( args[1], args[2] ); + case BOTLIB_AI_CHARACTERISTIC_BINTEGER: + return botlib_export->ai.Characteristic_BInteger( args[1], args[2], args[3], args[4] ); + case BOTLIB_AI_CHARACTERISTIC_STRING: + botlib_export->ai.Characteristic_String( args[1], args[2], (char *)VMA(3), args[4] ); + return 0; + + case BOTLIB_AI_ALLOC_CHAT_STATE: + return botlib_export->ai.BotAllocChatState(); + case BOTLIB_AI_FREE_CHAT_STATE: + botlib_export->ai.BotFreeChatState( args[1] ); + return 0; + case BOTLIB_AI_QUEUE_CONSOLE_MESSAGE: + botlib_export->ai.BotQueueConsoleMessage( args[1], args[2], (char *)VMA(3) ); + return 0; + case BOTLIB_AI_REMOVE_CONSOLE_MESSAGE: + botlib_export->ai.BotRemoveConsoleMessage( args[1], args[2] ); + return 0; + case BOTLIB_AI_NEXT_CONSOLE_MESSAGE: + return botlib_export->ai.BotNextConsoleMessage( args[1], (struct bot_consolemessage_s *)VMA(2) ); + case BOTLIB_AI_NUM_CONSOLE_MESSAGE: + return botlib_export->ai.BotNumConsoleMessages( args[1] ); + case BOTLIB_AI_INITIAL_CHAT: + botlib_export->ai.BotInitialChat( args[1], (char *)VMA(2), args[3], (char *)VMA(4), (char *)VMA(5), (char *)VMA(6), (char *)VMA(7), (char *)VMA(8), (char *)VMA(9), (char *)VMA(10), (char *)VMA(11) ); + return 0; + case BOTLIB_AI_NUM_INITIAL_CHATS: + return botlib_export->ai.BotNumInitialChats( args[1], (char *)VMA(2) ); + case BOTLIB_AI_REPLY_CHAT: + return botlib_export->ai.BotReplyChat( args[1], (char *)VMA(2), args[3], args[4], (char *)VMA(5), (char *)VMA(6), (char *)VMA(7), (char *)VMA(8), (char *)VMA(9), (char *)VMA(10), (char *)VMA(11), (char *)VMA(12) ); + case BOTLIB_AI_CHAT_LENGTH: + return botlib_export->ai.BotChatLength( args[1] ); + case BOTLIB_AI_ENTER_CHAT: + botlib_export->ai.BotEnterChat( args[1], args[2], args[3] ); + return 0; + case BOTLIB_AI_GET_CHAT_MESSAGE: + botlib_export->ai.BotGetChatMessage( args[1], (char *)VMA(2), args[3] ); + return 0; + case BOTLIB_AI_STRING_CONTAINS: + return botlib_export->ai.StringContains( (char *)VMA(1), (char *)VMA(2), args[3] ); + case BOTLIB_AI_FIND_MATCH: + return botlib_export->ai.BotFindMatch( (char *)VMA(1), (struct bot_match_s *)VMA(2), args[3] ); + case BOTLIB_AI_MATCH_VARIABLE: + botlib_export->ai.BotMatchVariable( (struct bot_match_s *)VMA(1), args[2], (char *)VMA(3), args[4] ); + return 0; + case BOTLIB_AI_UNIFY_WHITE_SPACES: + botlib_export->ai.UnifyWhiteSpaces( (char *)VMA(1) ); + return 0; + case BOTLIB_AI_REPLACE_SYNONYMS: + botlib_export->ai.BotReplaceSynonyms( (char *)VMA(1), args[2] ); + return 0; + case BOTLIB_AI_LOAD_CHAT_FILE: + return botlib_export->ai.BotLoadChatFile( args[1], (char *)VMA(2), (char *)VMA(3) ); + case BOTLIB_AI_SET_CHAT_GENDER: + botlib_export->ai.BotSetChatGender( args[1], args[2] ); + return 0; + case BOTLIB_AI_SET_CHAT_NAME: + botlib_export->ai.BotSetChatName( args[1], (char *)VMA(2), args[3] ); + return 0; + + case BOTLIB_AI_RESET_GOAL_STATE: + botlib_export->ai.BotResetGoalState( args[1] ); + return 0; + case BOTLIB_AI_RESET_AVOID_GOALS: + botlib_export->ai.BotResetAvoidGoals( args[1] ); + return 0; + case BOTLIB_AI_REMOVE_FROM_AVOID_GOALS: + botlib_export->ai.BotRemoveFromAvoidGoals( args[1], args[2] ); + return 0; + case BOTLIB_AI_PUSH_GOAL: + botlib_export->ai.BotPushGoal( args[1], (struct bot_goal_s *)VMA(2) ); + return 0; + case BOTLIB_AI_POP_GOAL: + botlib_export->ai.BotPopGoal( args[1] ); + return 0; + case BOTLIB_AI_EMPTY_GOAL_STACK: + botlib_export->ai.BotEmptyGoalStack( args[1] ); + return 0; + case BOTLIB_AI_DUMP_AVOID_GOALS: + botlib_export->ai.BotDumpAvoidGoals( args[1] ); + return 0; + case BOTLIB_AI_DUMP_GOAL_STACK: + botlib_export->ai.BotDumpGoalStack( args[1] ); + return 0; + case BOTLIB_AI_GOAL_NAME: + botlib_export->ai.BotGoalName( args[1], (char *)VMA(2), args[3] ); + return 0; + case BOTLIB_AI_GET_TOP_GOAL: + return botlib_export->ai.BotGetTopGoal( args[1], (struct bot_goal_s *)VMA(2) ); + case BOTLIB_AI_GET_SECOND_GOAL: + return botlib_export->ai.BotGetSecondGoal( args[1], (struct bot_goal_s *)VMA(2) ); + case BOTLIB_AI_CHOOSE_LTG_ITEM: + return botlib_export->ai.BotChooseLTGItem( args[1], (float *)VMA(2), (int *)VMA(3), args[4] ); + case BOTLIB_AI_CHOOSE_NBG_ITEM: + return botlib_export->ai.BotChooseNBGItem( args[1], (float *)VMA(2), (int *)VMA(3), args[4], (struct bot_goal_s *)VMA(5), VMF(6) ); + case BOTLIB_AI_TOUCHING_GOAL: + return botlib_export->ai.BotTouchingGoal( (float *)VMA(1), (struct bot_goal_s *)VMA(2) ); + case BOTLIB_AI_ITEM_GOAL_IN_VIS_BUT_NOT_VISIBLE: + return botlib_export->ai.BotItemGoalInVisButNotVisible( args[1], (float *)VMA(2), (float *)VMA(3), (struct bot_goal_s *)VMA(4) ); + case BOTLIB_AI_GET_LEVEL_ITEM_GOAL: + return botlib_export->ai.BotGetLevelItemGoal( args[1], (char *)VMA(2), (struct bot_goal_s *)VMA(3) ); + case BOTLIB_AI_GET_NEXT_CAMP_SPOT_GOAL: + return botlib_export->ai.BotGetNextCampSpotGoal( args[1], (struct bot_goal_s *)VMA(2) ); + case BOTLIB_AI_GET_MAP_LOCATION_GOAL: + return botlib_export->ai.BotGetMapLocationGoal( (char *)VMA(1), (struct bot_goal_s *)VMA(2) ); + case BOTLIB_AI_AVOID_GOAL_TIME: + return FloatAsInt( botlib_export->ai.BotAvoidGoalTime( args[1], args[2] ) ); + case BOTLIB_AI_SET_AVOID_GOAL_TIME: + botlib_export->ai.BotSetAvoidGoalTime( args[1], args[2], VMF(3)); + return 0; + case BOTLIB_AI_INIT_LEVEL_ITEMS: + botlib_export->ai.BotInitLevelItems(); + return 0; + case BOTLIB_AI_UPDATE_ENTITY_ITEMS: + botlib_export->ai.BotUpdateEntityItems(); + return 0; + case BOTLIB_AI_LOAD_ITEM_WEIGHTS: + return botlib_export->ai.BotLoadItemWeights( args[1], (char *)VMA(2) ); + case BOTLIB_AI_FREE_ITEM_WEIGHTS: + botlib_export->ai.BotFreeItemWeights( args[1] ); + return 0; + case BOTLIB_AI_INTERBREED_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotInterbreedGoalFuzzyLogic( args[1], args[2], args[3] ); + return 0; + case BOTLIB_AI_SAVE_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotSaveGoalFuzzyLogic( args[1], (char *)VMA(2) ); + return 0; + case BOTLIB_AI_MUTATE_GOAL_FUZZY_LOGIC: + botlib_export->ai.BotMutateGoalFuzzyLogic( args[1], VMF(2) ); + return 0; + case BOTLIB_AI_ALLOC_GOAL_STATE: + return botlib_export->ai.BotAllocGoalState( args[1] ); + case BOTLIB_AI_FREE_GOAL_STATE: + botlib_export->ai.BotFreeGoalState( args[1] ); + return 0; + + case BOTLIB_AI_RESET_MOVE_STATE: + botlib_export->ai.BotResetMoveState( args[1] ); + return 0; + case BOTLIB_AI_ADD_AVOID_SPOT: + botlib_export->ai.BotAddAvoidSpot( args[1], (float *)VMA(2), VMF(3), args[4] ); + return 0; + case BOTLIB_AI_MOVE_TO_GOAL: + botlib_export->ai.BotMoveToGoal( (struct bot_moveresult_s *)VMA(1), args[2], (struct bot_goal_s *)VMA(3), args[4] ); + return 0; + case BOTLIB_AI_MOVE_IN_DIRECTION: + return botlib_export->ai.BotMoveInDirection( args[1], (float *)VMA(2), VMF(3), args[4] ); + case BOTLIB_AI_RESET_AVOID_REACH: + botlib_export->ai.BotResetAvoidReach( args[1] ); + return 0; + case BOTLIB_AI_RESET_LAST_AVOID_REACH: + botlib_export->ai.BotResetLastAvoidReach( args[1] ); + return 0; + case BOTLIB_AI_REACHABILITY_AREA: + return botlib_export->ai.BotReachabilityArea( (float *)VMA(1), args[2] ); + case BOTLIB_AI_MOVEMENT_VIEW_TARGET: + return botlib_export->ai.BotMovementViewTarget( args[1], (struct bot_goal_s *)VMA(2), args[3], VMF(4), (float *)VMA(5) ); + case BOTLIB_AI_PREDICT_VISIBLE_POSITION: + return botlib_export->ai.BotPredictVisiblePosition( (float *)VMA(1), args[2], (struct bot_goal_s *)VMA(3), args[4], (float *)VMA(5) ); + case BOTLIB_AI_ALLOC_MOVE_STATE: + return botlib_export->ai.BotAllocMoveState(); + case BOTLIB_AI_FREE_MOVE_STATE: + botlib_export->ai.BotFreeMoveState( args[1] ); + return 0; + case BOTLIB_AI_INIT_MOVE_STATE: + botlib_export->ai.BotInitMoveState( args[1], (struct bot_initmove_s *)VMA(2) ); + return 0; + + case BOTLIB_AI_CHOOSE_BEST_FIGHT_WEAPON: + return botlib_export->ai.BotChooseBestFightWeapon( args[1], (int *)VMA(2) ); + case BOTLIB_AI_GET_WEAPON_INFO: + botlib_export->ai.BotGetWeaponInfo( args[1], args[2], (struct weaponinfo_s *)VMA(3) ); + return 0; + case BOTLIB_AI_LOAD_WEAPON_WEIGHTS: + return botlib_export->ai.BotLoadWeaponWeights( args[1], (char *)VMA(2) ); + case BOTLIB_AI_ALLOC_WEAPON_STATE: + return botlib_export->ai.BotAllocWeaponState(); + case BOTLIB_AI_FREE_WEAPON_STATE: + botlib_export->ai.BotFreeWeaponState( args[1] ); + return 0; + case BOTLIB_AI_RESET_WEAPON_STATE: + botlib_export->ai.BotResetWeaponState( args[1] ); + return 0; + + case BOTLIB_AI_GENETIC_PARENTS_AND_CHILD_SELECTION: + return botlib_export->ai.GeneticParentsAndChildSelection(args[1], (float *)VMA(2), (int *)VMA(3), (int *)VMA(4), (int *)VMA(5)); + + case G_R_REGISTERSKIN: + return RE_RegisterServerSkin((const char *)VMA(1)); + + case G_G2_LISTBONES: + G2API_ListBones( (CGhoul2Info *) VMA(1), args[2]); + return 0; + + case G_G2_LISTSURFACES: + G2API_ListSurfaces( (CGhoul2Info *) args[1] ); + return 0; + + case G_G2_HAVEWEGHOULMODELS: + return G2API_HaveWeGhoul2Models( *((CGhoul2Info_v *)args[1]) ); + + case G_G2_SETMODELS: + G2API_SetGhoul2ModelIndexes( *((CGhoul2Info_v *)args[1]),(qhandle_t *)VMA(2),(qhandle_t *)VMA(3)); + return 0; + + case G_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 G_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 G_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 G_G2_INITGHOUL2MODEL: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 1; +#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 G_G2_SETSKIN: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + int modelIndex = args[2]; + + return G2API_SetSkin(&g2[modelIndex], args[3], args[4]); + } + + case G_G2_SIZE: + return G2API_Ghoul2Size ( *((CGhoul2Info_v *)args[1]) ); + break; + + case G_G2_ADDBOLT: + return G2API_AddBolt(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3)); + + case G_G2_SETBOLTINFO: + G2API_SetBoltInfo(*((CGhoul2Info_v *)args[1]), args[2], args[3]); + return 0; + + case G_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 G_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 G_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 G_G2_GETGLANAME: + //return (int)G2API_GetGLAName(*((CGhoul2Info_v *)args[1]), args[2]); + { //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(3)); + char *local; + local = G2API_GetGLAName(*((CGhoul2Info_v *)args[1]), args[2]); + if (local) + { + strcpy(point, local); + } + } + + return 0; + + case G_G2_COPYGHOUL2INSTANCE: + return (int)G2API_CopyGhoul2Instance(*((CGhoul2Info_v *)args[1]), *((CGhoul2Info_v *)args[2]), args[3]); + + case G_G2_COPYSPECIFICGHOUL2MODEL: + G2API_CopySpecificG2Model(*((CGhoul2Info_v *)args[1]), args[2], *((CGhoul2Info_v *)args[3]), args[4]); + return 0; + + case G_G2_DUPLICATEGHOUL2INSTANCE: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 1; +#endif + G2API_DuplicateGhoul2Instance(*((CGhoul2Info_v *)args[1]), (CGhoul2Info_v **)VMA(2)); + return 0; + + case G_G2_HASGHOUL2MODELONINDEX: + //return (int)G2API_HasGhoul2ModelOnIndex((CGhoul2Info_v **)args[1], args[2]); + return (int)G2API_HasGhoul2ModelOnIndex((CGhoul2Info_v **)VMA(1), args[2]); + + case G_G2_REMOVEGHOUL2MODEL: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 1; +#endif + //return (int)G2API_RemoveGhoul2Model((CGhoul2Info_v **)args[1], args[2]); + return (int)G2API_RemoveGhoul2Model((CGhoul2Info_v **)VMA(1), args[2]); + + case G_G2_REMOVEGHOUL2MODELS: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 1; +#endif + //return (int)G2API_RemoveGhoul2Models((CGhoul2Info_v **)args[1]); + return (int)G2API_RemoveGhoul2Models((CGhoul2Info_v **)VMA(1)); + + case G_G2_CLEANMODELS: +#ifdef _FULL_G2_LEAK_CHECKING + g_G2AllocServer = 1; +#endif + G2API_CleanGhoul2Models((CGhoul2Info_v **)VMA(1)); + // G2API_CleanGhoul2Models((CGhoul2Info_v **)args[1]); + return 0; + + case G_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), + G2VertSpaceServer, + args[10], + args[11], + VMF(12) ); + return 0; + + case G_G2_SETROOTSURFACE: + return G2API_SetRootSurface(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3)); + + case G_G2_SETSURFACEONOFF: + return G2API_SetSurfaceOnOff(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), /*(const int)VMA(3)*/args[3]); + + case G_G2_SETNEWORIGIN: + return G2API_SetNewOrigin(*((CGhoul2Info_v *)args[1]), /*(const int)VMA(2)*/args[2]); + + case G_G2_DOESBONEEXIST: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + return G2API_DoesBoneExist(&g2[args[2]], (const char *)VMA(3)); + } + + case G_G2_GETSURFACERENDERSTATUS: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + + return G2API_GetSurfaceRenderStatus(&g2[args[2]], (const char *)VMA(3)); + } + + case G_G2_ABSURDSMOOTHING: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + + G2API_AbsurdSmoothing(g2, (qboolean)args[2]); + } + return 0; + + case G_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 G_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 G_G2_RAGPCJCONSTRAINT: + return G2API_RagPCJConstraint(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), (float *)VMA(3), (float *)VMA(4)); + case G_G2_RAGPCJGRADIENTSPEED: + return G2API_RagPCJGradientSpeed(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), VMF(3)); + case G_G2_RAGEFFECTORGOAL: + return G2API_RagEffectorGoal(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), (float *)VMA(3)); + case G_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 G_G2_RAGEFFECTORKICK: + return G2API_RagEffectorKick(*((CGhoul2Info_v *)args[1]), (const char *)VMA(2), (float *)VMA(3)); + case G_G2_RAGFORCESOLVE: + return G2API_RagForceSolve(*((CGhoul2Info_v *)args[1]), (qboolean)args[2]); + + case G_G2_SETBONEIKSTATE: + return G2API_SetBoneIKState(*((CGhoul2Info_v *)args[1]), args[2], (const char *)VMA(3), args[4], (sharedSetBoneIKStateParams_t *)VMA(5)); + case G_G2_IKMOVE: + return G2API_IKMove(*((CGhoul2Info_v *)args[1]), args[2], (sharedIKMoveParams_t *)VMA(3)); + + case G_G2_REMOVEBONE: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + + return G2API_RemoveBone(&g2[args[3]], (const char *)VMA(2)); + } + + case G_G2_ATTACHINSTANCETOENTNUM: + { + G2API_AttachInstanceToEntNum(*((CGhoul2Info_v *)args[1]), args[2], (qboolean)args[3]); + } + return 0; + case G_G2_CLEARATTACHEDINSTANCE: + G2API_ClearAttachedInstance(args[1]); + return 0; + case G_G2_CLEANENTATTACHMENTS: + G2API_CleanEntAttachments(); + return 0; + case G_G2_OVERRIDESERVER: + { + CGhoul2Info_v &g2 = *((CGhoul2Info_v *)args[1]); + return G2API_OverrideServerWithClientData(&g2[0]); + } + + case G_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 G_SET_ACTIVE_SUBBSP: + SV_SetActiveSubBSP(args[1]); + return 0; + + case G_RMG_INIT: + if (com_RMG && com_RMG->integer) + { + if (!TheRandomMissionManager) + { + TheRandomMissionManager = new CRMManager; + } + TheRandomMissionManager->SetLandScape(cmg.landScape); + if (TheRandomMissionManager->LoadMission(qtrue)) + { + TheRandomMissionManager->SpawnMission(qtrue); + } +// cmg.landScape->UpdatePatches(); + } + return 0; + + case G_CM_REGISTER_TERRAIN: + return CM_RegisterTerrain((const char *)VMA(1), true)->GetTerrainId(); + + case G_BOT_UPDATEWAYPOINTS: + SV_BotWaypointReception(args[1], (wpobject_t **)VMA(2)); + return 0; + case G_BOT_CALCULATEPATHS: + SV_BotCalculatePaths(args[1]); + return 0; + + case G_GET_ENTITY_TOKEN: + return SV_GetEntityToken((char *)VMA(1), args[2]); + + default: + Com_Error( ERR_DROP, "Bad game system trap: %i", args[0] ); + } + return -1; +} + +/* +=============== +SV_ShutdownGameProgs + +Called every time a map changes +=============== +*/ +void SV_ShutdownGameProgs( void ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_SHUTDOWN, qfalse ); + VM_Free( gvm ); + gvm = NULL; +} + +/* +================== +SV_InitGameVM + +Called for both a full init and a restart +================== +*/ +static void SV_InitGameVM( qboolean restart ) { + int i; + + // start the entity parsing at the beginning + sv.entityParsePoint = CM_EntityString(); + + // use the current msec count for a random seed + // init for this gamestate + VM_Call( gvm, GAME_INIT, svs.time, Com_Milliseconds(), restart ); + + // clear all gentity pointers that might still be set from + // a previous level + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + svs.clients[i].gentity = NULL; + } +} + + + +/* +=================== +SV_RestartGameProgs + +Called on a map_restart, but not on a normal map change +=================== +*/ +void SV_RestartGameProgs( void ) { + if ( !gvm ) { + return; + } + VM_Call( gvm, GAME_SHUTDOWN, qtrue ); + + // do a restart instead of a free + gvm = VM_Restart( gvm ); + if ( !gvm ) { // bk001212 - as done below + Com_Error( ERR_FATAL, "VM_Restart on game failed" ); + } + + SV_InitGameVM( qtrue ); +} + + +/* +=============== +SV_InitGameProgs + +Called on a normal map change, not on a map_restart +=============== +*/ +void SV_InitGameProgs( void ) { + cvar_t *var; + //FIXME these are temp while I make bots run in vm + extern int bot_enable; + + var = Cvar_Get( "bot_enable", "1", CVAR_LATCH ); + if ( var ) { + bot_enable = var->integer; + } + else { + bot_enable = 0; + } + + if ( !Cvar_VariableValue("fs_restrict") && !com_dedicated->integer && !Sys_CheckCD() ) + { + Com_Error( ERR_NEED_CD, SE_GetString("CON_TEXT_NEED_CD") ); //"Game CD not in drive" ); + } + + // load the dll or bytecode + gvm = VM_Create( "jampgame", SV_GameSystemCalls, (vmInterpret_t)(int)Cvar_VariableValue( "vm_game" ) ); + if ( !gvm ) { + Com_Error( ERR_FATAL, "VM_Create on game failed" ); + } + + SV_InitGameVM( qfalse ); +} + + +/* +==================== +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 (qboolean)VM_Call( gvm, GAME_CONSOLE_COMMAND ); +} + diff --git a/codemp/server/sv_init.cpp b/codemp/server/sv_init.cpp new file mode 100644 index 0000000..647666d --- /dev/null +++ b/codemp/server/sv_init.cpp @@ -0,0 +1,988 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "server.h" + +/* +Ghoul2 Insert Start +*/ +#if !defined(TR_LOCAL_H) + #include "../renderer/tr_local.h" +#endif + +#if !defined (MINIHEAP_H_INC) +#include "../qcommon/MiniHeap.h" +#endif + +#include "../qcommon/stringed_ingame.h" + +/* +=============== +SV_SetConfigstring + +=============== +*/ +void SV_SetConfigstring (int index, const char *val) { + int len, i; + int maxChunkSize = MAX_STRING_CHARS - 24; + client_t *client; + + 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.restarting ) { + + // send the data to all relevent clients + for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { + if ( client->state < CS_PRIMED ) { + continue; + } + // do not always send server info to all clients + if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { + continue; + } + + len = strlen( val ); + if( len >= maxChunkSize ) { + int sent = 0; + int remaining = len; + char *cmd; + char buf[MAX_STRING_CHARS]; + + while (remaining > 0 ) { + if ( sent == 0 ) { + cmd = "bcs0"; + } + else if( remaining < maxChunkSize ) { + cmd = "bcs2"; + } + else { + cmd = "bcs1"; + } + Q_strncpyz( buf, &val[sent], maxChunkSize ); + + SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, index, buf ); + + sent += (maxChunkSize - 1); + remaining -= (maxChunkSize - 1); + } + } else { + // standard cs, just send it + SV_SendServerCommand( client, "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_AddConfigstring + +================ +*/ +int SV_AddConfigstring (const char *name, int start, int max) +{ + int i; + + if (!name || !name[0]) + { + return 0; + } + + if (name[0] == '/' || name[0] == '\\') + { +#if _DEBUG + Com_DPrintf( "WARNING: Leading slash on '%s'\n", name); +#endif + name++; + + if (!name[0]) + { + return 0; + } + } + + for (i=1 ; i < max ; i++) + { + if (sv.configstrings[start+i][0] == 0) + { // Didn't find it + SV_SetConfigstring ((start+i), name); + break; + } + else if (!Q_stricmp(sv.configstrings[start+i], name)) + { + return i; + } + } + + return 0; + +} + +/* +=============== +SV_SetUserinfo + +=============== +*/ +void SV_SetUserinfo( int index, const char *val ) { + if ( index < 0 || index >= sv_maxclients->integer ) { + 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 ) ); + Q_strncpyz( svs.clients[index].name, Info_ValueForKey( val, "name" ), sizeof(svs.clients[index].name) ); +} + + + +/* +=============== +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 >= sv_maxclients->integer ) { + 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 ) { + sharedEntity_t *svent; + int entnum; + + for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) { + svent = SV_GentityNum(entnum); + if (!svent->r.linked) { + continue; + } + svent->s.number = entnum; + + // + // take current state as baseline + // + sv.svEntities[entnum].baseline = svent->s; + } +} + + +/* +=============== +SV_BoundMaxClients + +=============== +*/ +void SV_BoundMaxClients( int minimum ) { + // get the current maxclients value + Cvar_Get( "sv_maxclients", "8", 0 ); + + sv_maxclients->modified = qfalse; + + if ( sv_maxclients->integer < minimum ) { + Cvar_Set( "sv_maxclients", va("%i", minimum) ); + } else if ( sv_maxclients->integer > MAX_CLIENTS ) { + Cvar_Set( "sv_maxclients", va("%i", MAX_CLIENTS) ); + } +} + + +/* +=============== +SV_Startup + +Called when a host starts a map when it wasn't running +one before. Successive map or map_restart commands will +NOT cause this to be called, unless the game is exited to +the menu system first. +=============== +*/ +void SV_Startup( void ) { + if ( svs.initialized ) { + Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); + } + SV_BoundMaxClients( 1 ); + + svs.clients = (struct client_s *)Z_Malloc (sizeof(client_t) * sv_maxclients->integer, TAG_CLIENTS, qtrue ); + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + Cvar_Set( "r_ghoul2animsmooth", "0"); + Cvar_Set( "r_ghoul2unsqashaftersmooth", "0"); + + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } + svs.initialized = qtrue; + + Cvar_Set( "sv_running", "1" ); + +} + +/* +Ghoul2 Insert Start +*/ + + void SV_InitSV(void) +{ + // clear out most of the sv struct + memset(&sv, 0, (sizeof(sv))); + sv.mLocalSubBSPIndex = -1; +} +/* +Ghoul2 Insert End +*/ + +/* +================== +SV_ChangeMaxClients +================== +*/ +void SV_ChangeMaxClients( void ) { + int oldMaxClients; + int i; + client_t *oldClients; + int count; + + // get the highest client number in use + count = 0; + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + if (i > count) + count = i; + } + } + count++; + + oldMaxClients = sv_maxclients->integer; + // never go below the highest client number in use + SV_BoundMaxClients( count ); + // if still the same + if ( sv_maxclients->integer == oldMaxClients ) { + return; + } + + oldClients = (struct client_s *)Hunk_AllocateTempMemory( count * sizeof(client_t) ); + // copy the clients to hunk memory + for ( i = 0 ; i < count ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + oldClients[i] = svs.clients[i]; + } + else { + Com_Memset(&oldClients[i], 0, sizeof(client_t)); + } + } + + // free old clients arrays + Z_Free( svs.clients ); + + // allocate new clients + svs.clients = (struct client_s *)Z_Malloc ( sv_maxclients->integer * sizeof(client_t), TAG_CLIENTS, qtrue ); + Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) ); + + // copy the clients over + for ( i = 0 ; i < count ; i++ ) { + if ( oldClients[i].state >= CS_CONNECTED ) { + svs.clients[i] = oldClients[i]; + } + } + + // free the old clients on the hunk + Hunk_FreeTempMemory( oldClients ); + + // allocate new snapshot entities + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } +} + +/* +================ +SV_ClearServer +================ +*/ +void SV_ClearServer(void) { + int i; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( sv.configstrings[i] ) { + Z_Free( sv.configstrings[i] ); + } + } + +// CM_ClearMap(); + + /* +Ghoul2 Insert Start +*/ + + // nope, can't do this anymore.. sv contains entitystates with STL in them. +// memset (&sv, 0, sizeof(sv)); + SV_InitSV(); +/* +Ghoul2 Insert End +*/ +// Com_Memset (&sv, 0, sizeof(sv)); +} + +/* +================ +SV_TouchCGame + + touch the cgame.vm so that a pure client can load it if it's in a seperate pk3 +================ +*/ +void SV_TouchCGame(void) { + fileHandle_t f; + char filename[MAX_QPATH]; + + if (Cvar_VariableValue( "vm_cgame" )) + { + Com_sprintf( filename, sizeof(filename), "vm/%s.qvm", "cgame" ); + } + else + { + Com_sprintf( filename, sizeof(filename), "cgamex86.dll" ); + } + FS_FOpenFileRead( filename, &f, qfalse ); + if ( f ) { + FS_FCloseFile( f ); + } +} + +void SV_SendMapChange(void) +{ + int i; + + if (svs.clients) + { + for (i=0 ; iinteger ; i++) + { + if (svs.clients[i].state >= CS_CONNECTED) + { + if ( svs.clients[i].netchan.remoteAddress.type != NA_BOT ) + { + SV_SendClientMapChange( &svs.clients[i] ) ; + } + } + } + } +} + +void R_SVModelInit(); + + +#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 NAV_Free(void); +extern void CL_ClearLastLevel(void); +void SV_ClearLastLevel(void) +{ + CL_ClearLastLevel(); + NAV_Free(); + + int i; + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( sv.configstrings[i] ) { + Z_Free( sv.configstrings[i] ); + sv.configstrings[i] = NULL; + } + } + +} +#endif + + +/* +================ +SV_SpawnServer + +Change the server to a new map, taking all connected +clients along with it. +This is NOT called for map_restart +================ +*/ +extern CMiniHeap *G2VertSpaceServer; +extern CMiniHeap CMiniHeap_singleton; + +extern void RE_RegisterMedia_LevelLoadBegin(const char *psMapName, ForceReload_e eForceReload); +void SV_SpawnServer( char *server, qboolean killBots, ForceReload_e eForceReload ) { + int i; + int checksum; + qboolean isBot; + char systemInfo[16384]; + const char *p; + + SV_SendMapChange(); + + RE_RegisterMedia_LevelLoadBegin(server, eForceReload); + + // shut down the existing game if it is running + SV_ShutdownGameProgs(); + + Com_Printf ("------ Server Initialization ------\n"); + Com_Printf ("Server: %s\n",server); + +/* +Ghoul2 Insert Start +*/ + // de allocate the snapshot entities + if (svs.snapshotEntities) + { + delete[] svs.snapshotEntities; + svs.snapshotEntities = NULL; + } +/* +Ghoul2 Insert End +*/ + + SV_SendMapChange(); + +#ifdef _XBOX + // disable vsync during load for speed + qglDisable(GL_VSYNC); +#endif + + // if not running a dedicated server CL_MapLoading will connect the client to the server + // also print some status stuff + CL_MapLoading(); + +#ifndef DEDICATED + // make sure all the client stuff is unloaded + CL_ShutdownAll(); +#endif + + CM_ClearMap(); + +#ifdef _XBOX +// extern qboolean RE_RegisterImages_LevelLoadEnd(void); +// RE_RegisterImages_LevelLoadEnd(); + R_DeleteTextures(); +#endif + + // clear the whole hunk because we're (re)loading the server + Hunk_Clear(); + +#ifdef _XBOX + SV_ClearLastLevel(); +#endif + + R_InitSkins(); + R_InitShaders(qtrue); + +#if defined(_XBOX) && defined(_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 + + // init client structures and svs.numSnapshotEntities + if ( !Cvar_VariableValue("sv_running") ) { + SV_Startup(); + } else { + // check for maxclients change + if ( sv_maxclients->modified ) { + SV_ChangeMaxClients(); + } + } + + SV_SendMapChange(); + +/* +Ghoul2 Insert Start +*/ + // clear out those shaders, images and Models as long as this + // isnt a dedicated server. + /* + if ( !com_dedicated->integer ) + { +#ifndef DEDICATED + R_InitImages(); + + R_InitShaders(); + + R_ModelInit(); +#endif + } + else + */ + if (com_dedicated->integer) + { + R_SVModelInit(); + } + + SV_SendMapChange(); + + // clear pak references + FS_ClearPakReferences(0); + +/* +Ghoul2 Insert Start +*/ + // allocate the snapshot entities on the hunk +// svs.snapshotEntities = (struct entityState_s *)Hunk_Alloc( sizeof(entityState_t)*svs.numSnapshotEntities, h_high ); + svs.nextSnapshotEntities = 0; + + // allocate the snapshot entities + svs.snapshotEntities = new entityState_s[svs.numSnapshotEntities]; + // we CAN afford to do this here, since we know the STL vectors in Ghoul2 are empty + memset(svs.snapshotEntities, 0, sizeof(entityState_t)*svs.numSnapshotEntities); + +/* +Ghoul2 Insert End +*/ + + // 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", "map_restart 0"); +// Cvar_Set( "nextmap", va("map %s", server) ); + + // wipe the entire per-level structure + SV_ClearServer(); + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + sv.configstrings[i] = CopyString(""); + } + + //rww - RAGDOLL_BEGIN + G2API_SetTime(svs.time,0); + //rww - RAGDOLL_END + + // make sure we are not paused + Cvar_Set("cl_paused", "0"); + + // get a new checksum feed and restart the file system + srand(Com_Milliseconds()); + sv.checksumFeed = ( ((int) rand() << 16) ^ rand() ) ^ Com_Milliseconds(); + FS_Restart( sv.checksumFeed ); + +#ifdef _XBOX + CL_StartHunkUsers(); + CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum ); +// RE_LoadWorldMap(va("maps/%s.bsp", server)); + + // Start up voice system if it isn't running yet. (ie, if we're on syslink) + if( !logged_on ) + g_Voice.Initialize(); +#else + CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum ); +#endif + + SV_SendMapChange(); + + // 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; + sv.restartedServerId = sv.serverId; + 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(); + + // don't allow a map_restart if game is modified + sv_gametype->modified = qfalse; + + // run a few frames to allow everything to settle + for ( i = 0 ;i < 3 ; i++ ) { + //rww - RAGDOLL_BEGIN + G2API_SetTime(svs.time,0); + //rww - RAGDOLL_END + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + SV_BotFrame( svs.time ); + svs.time += 100; + } + //rww - RAGDOLL_BEGIN + G2API_SetTime(svs.time,0); + //rww - RAGDOLL_END + + // create a baseline for more efficient communications + SV_CreateBaseline (); + + for (i=0 ; iinteger ; i++) { + // send the new gamestate to all connected clients + if (svs.clients[i].state >= CS_CONNECTED) { + char *denied; + + if ( svs.clients[i].netchan.remoteAddress.type == NA_BOT ) { + if ( killBots ) { + SV_DropClient( &svs.clients[i], "" ); + continue; + } + isBot = qtrue; + } + else { + isBot = qfalse; + } + + // connect the client again + denied = (char *)VM_ExplicitArgPtr( gvm, VM_Call( gvm, GAME_CLIENT_CONNECT, i, qfalse, isBot ) ); // firstTime = qfalse + if ( denied ) { + // this generally shouldn't happen, because the client + // was connected before the level change + SV_DropClient( &svs.clients[i], denied ); + } else { + if( !isBot ) { + // when we get the next packet from a connected client, + // the new gamestate will be sent + svs.clients[i].state = CS_CONNECTED; + } + else { + client_t *client; + sharedEntity_t *ent; + + client = &svs.clients[i]; + client->state = CS_ACTIVE; + ent = SV_GentityNum( i ); + ent->s.number = i; + client->gentity = ent; + + client->deltaMessage = -1; + client->nextSnapshotTime = svs.time; // generate a snapshot immediately + + VM_Call( gvm, GAME_CLIENT_BEGIN, i ); + } + } + } + } + + // run another frame to allow things to look at all the players + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + SV_BotFrame( svs.time ); + svs.time += 100; + //rww - RAGDOLL_BEGIN + G2API_SetTime(svs.time,0); + //rww - RAGDOLL_END + + if ( sv_pure->integer ) { + // the server sends these to the clients so they will only + // load pk3s also loaded at the server + p = FS_LoadedPakChecksums(); + Cvar_Set( "sv_paks", p ); + if (strlen(p) == 0) { + Com_Printf( "WARNING: sv_pure set but no PK3 files loaded\n" ); + } + p = FS_LoadedPakNames(); + Cvar_Set( "sv_pakNames", p ); + + // if a dedicated pure server we need to touch the cgame because it could be in a + // seperate pk3 file and the client will need to load the latest cgame.qvm + if ( com_dedicated->integer ) { + SV_TouchCGame(); + } + } + else { + Cvar_Set( "sv_paks", "" ); + Cvar_Set( "sv_pakNames", "" ); + } + // the server sends these to the clients so they can figure + // out which pk3s should be auto-downloaded + p = FS_ReferencedPakChecksums(); + Cvar_Set( "sv_referencedPaks", p ); + p = FS_ReferencedPakNames(); + Cvar_Set( "sv_referencedPakNames", p ); + + // save systeminfo and serverinfo strings + Q_strncpyz( systemInfo, Cvar_InfoString_Big( CVAR_SYSTEMINFO ), sizeof( systemInfo ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + SV_SetConfigstring( CS_SYSTEMINFO, 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 + SV_Heartbeat_f(); + + Hunk_SetMark(); + + /* MrE: 2000-09-13: now called in CL_DownloadsComplete + // don't call when running dedicated + if ( !com_dedicated->integer ) { + // note that this is called after setting the hunk mark with Hunk_SetMark + CL_StartHunkUsers(); + } + */ +} + + +/* +=============== +SV_Init + +Only called at main exe startup, not for each game +=============== +*/ +void SV_BotInitBotLib(void); + +void SV_Init (void) { + SV_AddOperatorCommands (); + + // serverinfo vars + Cvar_Get ("dmflags", "0", CVAR_SERVERINFO); + Cvar_Get ("fraglimit", "20", CVAR_SERVERINFO); + Cvar_Get ("timelimit", "0", CVAR_SERVERINFO); + + // Get these to establish them and to make sure they have a default before the menus decide to stomp them. + Cvar_Get ("g_maxHolocronCarry", "3", CVAR_SERVERINFO); + Cvar_Get ("g_privateDuel", "1", CVAR_SERVERINFO ); + Cvar_Get ("g_saberLocking", "1", CVAR_SERVERINFO ); + Cvar_Get ("g_maxForceRank", "6", CVAR_SERVERINFO ); + Cvar_Get ("duel_fraglimit", "10", CVAR_SERVERINFO); + Cvar_Get ("g_forceBasedTeams", "0", CVAR_SERVERINFO); + Cvar_Get ("g_duelWeaponDisable", "1", CVAR_SERVERINFO); + + sv_gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO | CVAR_LATCH ); + sv_needpass = Cvar_Get ("g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM ); + Cvar_Get ("sv_keywords", "", CVAR_SERVERINFO); + Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); + sv_mapname = Cvar_Get ("mapname", "nomap", CVAR_SERVERINFO | CVAR_ROM); + sv_privateClients = Cvar_Get ("sv_privateClients", "0", CVAR_SERVERINFO); + sv_hostname = Cvar_Get ("sv_hostname", "*Jedi*", CVAR_SERVERINFO | CVAR_ARCHIVE ); + sv_maxclients = Cvar_Get ("sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH); + sv_maxRate = Cvar_Get ("sv_maxRate", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_minPing = Cvar_Get ("sv_minPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_maxPing = Cvar_Get ("sv_maxPing", "0", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_floodProtect = Cvar_Get ("sv_floodProtect", "1", CVAR_ARCHIVE | CVAR_SERVERINFO ); + sv_allowAnonymous = Cvar_Get ("sv_allowAnonymous", "0", CVAR_SERVERINFO); + + // systeminfo + Cvar_Get ("sv_cheats", "0", CVAR_SYSTEMINFO | CVAR_ROM ); + sv_serverid = Cvar_Get ("sv_serverid", "0", CVAR_SYSTEMINFO | CVAR_ROM ); +#ifndef DLL_ONLY // bk010216 - for DLL-only servers + sv_pure = Cvar_Get ("sv_pure", "1", CVAR_SYSTEMINFO ); +#else + sv_pure = Cvar_Get ("sv_pure", "0", CVAR_SYSTEMINFO | CVAR_INIT | CVAR_ROM ); +#endif + Cvar_Get ("sv_paks", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_pakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_referencedPaks", "", CVAR_SYSTEMINFO | CVAR_ROM ); + Cvar_Get ("sv_referencedPakNames", "", CVAR_SYSTEMINFO | CVAR_ROM ); + + // server vars + sv_rconPassword = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + sv_privatePassword = Cvar_Get ("sv_privatePassword", "", CVAR_TEMP ); + sv_fps = Cvar_Get ("sv_fps", "20", CVAR_TEMP ); + sv_timeout = Cvar_Get ("sv_timeout", "200", CVAR_TEMP ); + sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP ); + Cvar_Get ("nextmap", "", CVAR_TEMP ); + +#ifndef _XBOX // No master or downloads on Xbox + sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO); + sv_master[0] = Cvar_Get ("sv_master1", MASTER_SERVER_NAME, 0 ); + sv_master[1] = Cvar_Get ("sv_master2", "", CVAR_ARCHIVE ); + sv_master[2] = Cvar_Get ("sv_master3", "", CVAR_ARCHIVE ); + sv_master[3] = Cvar_Get ("sv_master4", "", CVAR_ARCHIVE ); + sv_master[4] = Cvar_Get ("sv_master5", "", CVAR_ARCHIVE ); +#endif + sv_reconnectlimit = Cvar_Get ("sv_reconnectlimit", "3", 0); + sv_showghoultraces = Cvar_Get ("sv_showghoultraces", "0", 0); + sv_showloss = Cvar_Get ("sv_showloss", "0", 0); + sv_padPackets = Cvar_Get ("sv_padPackets", "0", 0); + sv_killserver = Cvar_Get ("sv_killserver", "0", 0); + sv_mapChecksum = Cvar_Get ("sv_mapChecksum", "", CVAR_ROM); + +// sv_debugserver = Cvar_Get ("sv_debugserver", "0", 0); + + // initialize bot cvars so they are listed and can be set before loading the botlib + SV_BotInitCvars(); + + // init the botlib here because we need the pre-compiler in the UI + SV_BotInitBotLib(); + +#ifdef _XBOX + svs.clientRefNum = 0; +#endif + // 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 + G2VertSpaceServer = &CMiniHeap_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; + + // send it twice, ignoring rate + for ( j = 0 ; j < 2 ; j++ ) { + for (i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { + if (cl->state >= CS_CONNECTED) { + // don't send a disconnect to a local client + if ( cl->netchan.remoteAddress.type != NA_LOOPBACK ) { + SV_SendServerCommand( cl, "print \"%s\"", message ); + SV_SendServerCommand( cl, "disconnect" ); + } + // 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 ) +{ + if ( !com_sv_running || !com_sv_running->integer ) + { + return; + } + +// Com_Printf( "----- Server Shutdown -----\n" ); + + if ( svs.clients && !com_errorEntered ) { + SV_FinalMessage( finalmsg ); + } + + SV_RemoveOperatorCommands(); +#ifndef _XBOX // No master on Xbox + SV_MasterShutdown(); +#endif + SV_ShutdownGameProgs(); +/* +Ghoul2 Insert Start +*/ + // de allocate the snapshot entities + if (svs.snapshotEntities) + { + delete[] svs.snapshotEntities; + svs.snapshotEntities = NULL; + } + + // free current level + SV_ClearServer(); + CM_ClearMap();//jfm: add a clear here since it's commented out in clearServer. This prevents crashing cmShaderTable on exit. + + // free server static data + if ( svs.clients ) { + Z_Free( svs.clients ); + } + Com_Memset( &svs, 0, sizeof( svs ) ); + + Cvar_Set( "sv_running", "0" ); + Cvar_Set("ui_singlePlayerActive", "0"); + +// Com_Printf( "---------------------------\n" ); + +#ifdef _XBOX + // If we were advertising on Live, remove the listing. This also unregisters + // the server's key. SysLink keys are never unregistered, so we don't do anything + // special here for them. + if ( logged_on ) + XBL_MM_Shutdown(); + + // Tear down voice now if we're on system link (Live keeps it active) + if( !logged_on ) + g_Voice.Shutdown(); + + // Wipe our player list - this is important + memset( &xbOnlineInfo, 0, sizeof(xbOnlineInfo) ); +#endif + + // disconnect any local clients + CL_Disconnect( qfalse ); +} + diff --git a/codemp/server/sv_main.cpp b/codemp/server/sv_main.cpp new file mode 100644 index 0000000..16507a2 --- /dev/null +++ b/codemp/server/sv_main.cpp @@ -0,0 +1,937 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "server.h" + +//rww - RAGDOLL_BEGIN +#include "../ghoul2/ghoul2_shared.h" +//rww - RAGDOLL_END + +serverStatic_t svs; // persistant server info +server_t sv; // local server +vm_t *gvm = NULL; // game virtual machine // bk001212 init + +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_rconPassword; // password for remote server commands +cvar_t *sv_privatePassword; // password for the privateClient slots +cvar_t *sv_maxclients; +cvar_t *sv_privateClients; // number of clients reserved for password +cvar_t *sv_hostname; +#ifndef _XBOX // No master or downloads on Xbox +cvar_t *sv_allowDownload; +cvar_t *sv_master[MAX_MASTER_SERVERS]; // master server ip address +#endif +cvar_t *sv_reconnectlimit; // minimum seconds between connect messages +cvar_t *sv_showghoultraces; // report ghoul2 traces +cvar_t *sv_showloss; // report when usercmds are lost +cvar_t *sv_padPackets; // add nop bytes to messages +cvar_t *sv_killserver; // menu system can set to 1 to shut server down +cvar_t *sv_mapname; +cvar_t *sv_mapChecksum; +cvar_t *sv_serverid; +cvar_t *sv_maxRate; +cvar_t *sv_minPing; +cvar_t *sv_maxPing; +cvar_t *sv_gametype; +cvar_t *sv_pure; +cvar_t *sv_floodProtect; +cvar_t *sv_allowAnonymous; +cvar_t *sv_needpass; + +/* +============================================================================= + +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_ReplacePendingServerCommands + + This is ugly +====================== +*/ +int SV_ReplacePendingServerCommands( client_t *client, const char *cmd ) { + int i, index, csnum1, csnum2; + + for ( i = client->reliableSent+1; i <= client->reliableSequence; i++ ) { + index = i & ( MAX_RELIABLE_COMMANDS - 1 ); + // + if ( !Q_strncmp(cmd, client->reliableCommands[ index ], strlen("cs")) ) { + sscanf(cmd, "cs %i", &csnum1); + sscanf(client->reliableCommands[ index ], "cs %i", &csnum2); + if ( csnum1 == csnum2 ) { + Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) ); + /* + if ( client->netchan.remoteAddress.type != NA_BOT ) { + Com_Printf( "WARNING: client %i removed double pending config string %i: %s\n", client-svs.clients, csnum1, cmd ); + } + */ + return qtrue; + } + } + } + return qfalse; +} + +/* +====================== +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, i; + + // this is very ugly but it's also a waste to for instance send multiple config string updates + // for the same config string index in one snapshot +// if ( SV_ReplacePendingServerCommands( client, cmd ) ) { +// return; +// } + + client->reliableSequence++; + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + // we check == instead of >= so a broadcast print added by SV_DropClient() + // doesn't cause a recursive drop client + if ( client->reliableSequence - client->reliableAcknowledge == MAX_RELIABLE_COMMANDS + 1 ) { + Com_Printf( "===== pending server commands =====\n" ); + for ( i = client->reliableAcknowledge + 1 ; i <= client->reliableSequence ; i++ ) { + Com_Printf( "cmd %5d: %s\n", i, client->reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + Com_Printf( "cmd %5d: %s\n", i, cmd ); + SV_DropClient( client, "Server command overflow" ); + return; + } + index = client->reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + Q_strncpyz( client->reliableCommands[ index ], cmd, sizeof( client->reliableCommands[ index ] ) ); +} + + +/* +================= +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 QDECL SV_SendServerCommand(client_t *cl, const char *fmt, ...) { + va_list argptr; + byte message[MAX_MSGLEN]; + client_t *client; + int j; + + va_start (argptr,fmt); + vsprintf ((char *)message, fmt,argptr); + va_end (argptr); + + if ( cl != NULL ) { + SV_AddServerCommand( cl, (char *)message ); + return; + } + + // hack to echo broadcast prints to console + if ( com_dedicated->integer && !strncmp( (char *)message, "print", 5) ) { + Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) ); + } + + // send the data to all relevent clients + for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) { + if ( client->state < CS_PRIMED ) { + continue; + } + SV_AddServerCommand( client, (char *)message ); + } +} + + +/* +============================================================================== + +MASTER SERVER FUNCTIONS + +============================================================================== +*/ +#ifndef _XBOX // No master on Xbox +#define NEW_RESOLVE_DURATION 86400000 //24 hours +static int g_lastResolveTime[MAX_MASTER_SERVERS]; + +static inline bool SV_MasterNeedsResolving(int server, int time) +{ //refresh every so often regardless of if the actual address was modified -rww + if (g_lastResolveTime[server] > time) + { //time flowed backwards? + return true; + } + + if ((time-g_lastResolveTime[server]) > NEW_RESOLVE_DURATION) + { //it's time again + return true; + } + + return false; +} + +/* +================ +SV_MasterHeartbeat + +Send a message to the masters every few minutes to +let it know we are alive, and log information. +We will also have a heartbeat sent when a server +changes from empty to non-empty, and full to non-full, +but not on every player enter or exit. +================ +*/ +#define HEARTBEAT_MSEC 300*1000 +#define HEARTBEAT_GAME "QuakeArena-1" +void SV_MasterHeartbeat( void ) { + static netadr_t adr[MAX_MASTER_SERVERS]; + int i; + int time; + + // "dedicated 1" is for lan play, "dedicated 2" is for inet public play + if ( !com_dedicated || com_dedicated->integer != 2 ) { + return; // only dedicated servers send heartbeats + } + + // if not time yet, don't send anything + if ( svs.time < svs.nextHeartbeatTime ) { + return; + } + svs.nextHeartbeatTime = svs.time + HEARTBEAT_MSEC; + + //we need to use this instead of svs.time since svs.time resets over map changes (or rather + //every time the game restarts), and we don't really need to resolve every map change + time = Com_Milliseconds(); + + // send to group masters + for ( i = 0 ; i < MAX_MASTER_SERVERS ; i++ ) { + if ( !sv_master[i]->string[0] ) { + continue; + } + + // see if we haven't already resolved the name + // resolving usually causes hitches on win95, so only + // do it when needed + if ( sv_master[i]->modified || SV_MasterNeedsResolving(i, time) ) { + sv_master[i]->modified = qfalse; + + g_lastResolveTime[i] = time; + + Com_Printf( "Resolving %s\n", sv_master[i]->string ); + if ( !NET_StringToAdr( sv_master[i]->string, &adr[i] ) ) { + // if the address failed to resolve, clear it + // so we don't take repeated dns hits + Com_Printf( "Couldn't resolve address: %s\n", sv_master[i]->string ); + Cvar_Set( sv_master[i]->name, "" ); + sv_master[i]->modified = qfalse; + continue; + } + if ( !strstr( ":", sv_master[i]->string ) ) { + adr[i].port = BigShort( PORT_MASTER ); + } + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", sv_master[i]->string, + adr[i].ip[0], adr[i].ip[1], adr[i].ip[2], adr[i].ip[3], + BigShort( adr[i].port ) ); + } + + + Com_Printf ("Sending heartbeat to %s\n", sv_master[i]->string ); + // this command should be changed if the server info / status format + // ever incompatably changes + NET_OutOfBandPrint( NS_SERVER, adr[i], "heartbeat %s\n", HEARTBEAT_GAME ); + } +} + +/* +================= +SV_MasterShutdown + +Informs all masters that this server is going down +================= +*/ +void SV_MasterShutdown( void ) { + // send a hearbeat right now + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat(); + + // send it again to minimize chance of drops + svs.nextHeartbeatTime = -9999; + SV_MasterHeartbeat(); + + // when the master tries to poll the server, it won't respond, so + // it will be removed from the list +} +#endif // _XBOX - No master on Xbox + + +/* +============================================================================== + +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; + playerState_t *ps; + int statusLength; + int playerLength; + char infostring[MAX_INFO_STRING]; + + // ignore if we are in single player + /* + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) { + return; + } + */ + + strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); + + // echo back the parameter to status. so master servers can use it as a challenge + // to prevent timed spoofed reply packets that add ghost servers + Info_SetValueForKey( infostring, "challenge", Cmd_Argv(1) ); + + // add "demo" to the sv_keywords if restricted + if ( Cvar_VariableValue( "fs_restrict" ) ) { + char keywords[MAX_INFO_STRING]; + + Com_sprintf( keywords, sizeof( keywords ), "demo %s", + Info_ValueForKey( infostring, "sv_keywords" ) ); + Info_SetValueForKey( infostring, "sv_keywords", keywords ); + } + + status[0] = 0; + statusLength = 0; + + for (i=0 ; i < sv_maxclients->integer ; i++) { + cl = &svs.clients[i]; + if ( cl->state >= CS_CONNECTED ) { + ps = SV_GameClientNum( i ); + Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", + ps->persistant[PERS_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 +================ +*/ +void SVC_Info( netadr_t from ) { + int i, count, wDisable; + char *gamedir; + char infostring[MAX_INFO_STRING]; + + // ignore if we are in single player + /* + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) { + return; + } + */ + +#ifdef _XBOX + // don't send system link info if in Xbox Live + if (logged_on) + return; +#endif + + if (Cvar_VariableValue("ui_singlePlayerActive")) + { + return; + } + + // don't count privateclients + count = 0; + for ( i = sv_privateClients->integer ; i < sv_maxclients->integer ; 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", sv_maxclients->integer - sv_privateClients->integer ) ); + Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) ); + Info_SetValueForKey( infostring, "needpass", va("%i", sv_needpass->integer ) ); + Info_SetValueForKey( infostring, "truejedi", va("%i", Cvar_VariableIntegerValue( "g_jediVmerc" ) ) ); + if ( sv_gametype->integer == GT_DUEL || sv_gametype->integer == GT_POWERDUEL ) + { + wDisable = Cvar_VariableIntegerValue( "g_duelWeaponDisable" ); + } + else + { + wDisable = Cvar_VariableIntegerValue( "g_weaponDisable" ); + } + Info_SetValueForKey( infostring, "wdisable", va("%i", wDisable ) ); + Info_SetValueForKey( infostring, "fdisable", va("%i", Cvar_VariableIntegerValue( "g_forcePowerDisable" ) ) ); + //Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) ); + + if( sv_minPing->integer ) { + Info_SetValueForKey( infostring, "minPing", va("%i", sv_minPing->integer) ); + } + if( sv_maxPing->integer ) { + Info_SetValueForKey( infostring, "maxPing", va("%i", sv_maxPing->integer) ); + } + gamedir = Cvar_VariableString( "fs_game" ); + if( *gamedir ) { + Info_SetValueForKey( infostring, "game", gamedir ); + } + Info_SetValueForKey( infostring, "sv_allowAnonymous", va("%i", sv_allowAnonymous->integer) ); + +#ifdef _XBOX + // Include Xbox specific networking info + char sxnkid[XNKID_STRING_LEN]; + XNKIDToString(SysLink_GetXNKID(), sxnkid); + Info_SetValueForKey(infostring, "xnkid", sxnkid); + + char sxnkey[XNKEY_STRING_LEN]; + XNKEYToString(SysLink_GetXNKEY(), sxnkey); + Info_SetValueForKey(infostring, "xnkey", sxnkey); + + char sxnaddr[XNADDR_STRING_LEN]; + XnAddrToString(Net_GetXNADDR(), sxnaddr); + Info_SetValueForKey(infostring, "xnaddr", sxnaddr); +#endif + + NET_OutOfBandPrint( NS_SERVER, from, "infoResponse\n%s", infostring ); +} + +/* +================ +SVC_FlushRedirect + +================ +*/ +void SV_FlushRedirect( char *outputbuf ) { + NET_OutOfBandPrint( NS_SERVER, svs.redirectAddress, "print\n%s", outputbuf ); +} + +/* +=============== +SVC_RemoteCommand + +An rcon packet arrived from the network. +Shift down the remaining args +Redirect all printfs +=============== +*/ +void SVC_RemoteCommand( netadr_t from, msg_t *msg ) { + qboolean valid; + unsigned int i, time; + char remaining[1024]; +#define SV_OUTPUTBUF_LENGTH (MAX_MSGLEN - 16) + char sv_outputbuf[SV_OUTPUTBUF_LENGTH]; + static unsigned int lasttime = 0; + + time = Com_Milliseconds(); + if (time<(lasttime+500)) { + return; + } + lasttime = time; + + if ( !strlen( sv_rconPassword->string ) || + strcmp (Cmd_Argv(1), sv_rconPassword->string) ) { + valid = qfalse; + Com_DPrintf ("Bad rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); + } else { + valid = qtrue; + Com_DPrintf ("Rcon from %s:\n%s\n", NET_AdrToString (from), Cmd_Argv(2) ); + } + + // start redirecting all print outputs to the packet + svs.redirectAddress = from; + Com_BeginRedirect (sv_outputbuf, SV_OUTPUTBUF_LENGTH, SV_FlushRedirect); + + if ( !strlen( sv_rconPassword->string ) ) { + Com_Printf ("No rconpassword set.\n"); + } else if ( !valid ) { + Com_Printf ("Bad rconpassword.\n"); + } else { + remaining[0] = 0; + + for (i=2 ; idata[4], 7)) { + Huff_Decompress(msg, 12); + } + + s = MSG_ReadStringLine( msg ); + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + Com_DPrintf ("SV packet %s : %s\n", NET_AdrToString(from), c); + + if (!Q_stricmp(c, "getstatus")) { + SVC_Status( from ); + } else if (!Q_stricmp(c, "getinfo")) { + SVC_Info( from ); + } else if (!Q_stricmp(c, "getchallenge")) { + SV_GetChallenge( from ); + } else if (!Q_stricmp(c, "connect")) { + SV_DirectConnect( from ); +#ifndef _XBOX // No authorization on Xbox + } else if (!Q_stricmp(c, "ipAuthorize")) { + SV_AuthorizeIpPacket( from ); +#endif + } else if (!Q_stricmp(c, "rcon")) { + SVC_RemoteCommand( from, msg ); + } else if (!Q_stricmp(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_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // sequence number + qport = MSG_ReadShort( msg ) & 0xffff; + + // find which client the message is from + for (i=0, cl=svs.clients ; i < sv_maxclients->integer ; 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 (SV_Netchan_Process(cl, msg)) { + // zombie clients still need 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 = svs.time; // don't timeout + 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; + playerState_t *ps; + + for (i=0 ; i < sv_maxclients->integer ; i++) { + cl = &svs.clients[i]; + if ( cl->state != CS_ACTIVE ) { + cl->ping = 999; + continue; + } + if ( !cl->gentity ) { + cl->ping = 999; + continue; + } + if ( cl->gentity->r.svFlags & SVF_BOT ) { + cl->ping = 0; + continue; + } + + total = 0; + count = 0; + for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { + if ( cl->frames[j].messageAcked <= 0 ) { + continue; + } + delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; + 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 + ps = SV_GameClientNum( i ); + ps->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 = svs.time - 1000 * sv_timeout->integer; + zombiepoint = svs.time - 1000 * sv_zombietime->integer; + + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + // message times may be wrong across a changelevel + if (cl->lastPacketTime > svs.time) { + cl->lastPacketTime = svs.time; + } + + if (cl->state == CS_ZOMBIE + && cl->lastPacketTime < zombiepoint) { + Com_DPrintf( "Going from CS_ZOMBIE to CS_FREE for %s\n", cl->name ); + 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 ) { + int count; + client_t *cl; + int i; + + if ( !cl_paused->integer ) { + return qfalse; + } + + // only pause if there is just a single client connected + count = 0; + for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { + if ( cl->state >= CS_CONNECTED && cl->netchan.remoteAddress.type != NA_BOT ) { + count++; + } + } + + if ( count > 1 ) { + // don't pause + sv_paused->integer = 0; + return qfalse; + } + + sv_paused->integer = 1; + return qtrue; +} + +/* +================== +SV_CheckCvars +================== +*/ +void SV_CheckCvars( void ) { + static int lastMod = -1; + qboolean changed = qfalse; + + if ( sv_hostname->modificationCount != lastMod ) { + char hostname[MAX_INFO_STRING]; + char *c = hostname; + lastMod = sv_hostname->modificationCount; + + strcpy( hostname, sv_hostname->string ); + while( *c ) + { + if ( (*c == '\\') || (*c == ';') || (*c == '"')) + { + *c = '.'; + changed = qtrue; + } + c++; + } + if( changed ) + { + Cvar_Set("sv_hostname", hostname ); + } + + } +} + +/* +================== +SV_Frame + +Player movement occurs as a result of packet events, which +happen before SV_Frame is called +================== +*/ +void SV_Frame( int msec ) { + int frameMsec; + int startTime; + + // 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; + } + + // allow pause if only the local client is connected + if ( SV_CheckPaused() ) { + return; + } + + // 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; + + if (!com_dedicated->integer) SV_BotFrame( svs.time + sv.timeResidual ); + + if ( com_dedicated->integer && sv.timeResidual < frameMsec && (!com_timescale || com_timescale->value >= 1) ) { + // NET_Sleep will give the OS time slices until either get a packet + // or time enough for a server frame has gone by + NET_Sleep(frameMsec - sv.timeResidual); + return; + } + + // if time is about to hit the 32nd bit, kick all clients + // and clear sv.time, rather + // than checking for negative time wraparound everywhere. + // 2giga-milliseconds = 23 days, so it won't be too often + if ( svs.time > 0x70000000 ) { + SV_Shutdown( "Restarting server due to time wrapping" ); + //Cbuf_AddText( "vstr nextmap\n" ); + Cbuf_AddText( "map_restart 0\n" ); + return; + } + // this can happen considerably earlier when lots of clients play and the map doesn't change + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE - svs.numSnapshotEntities ) { + SV_Shutdown( "Restarting server due to numSnapshotEntities wrapping" ); + //Cbuf_AddText( "vstr nextmap\n" ); + Cbuf_AddText( "map_restart 0\n" ); + return; + } + + if( sv.restartTime && svs.time >= sv.restartTime ) { + sv.restartTime = 0; + Cbuf_AddText( "map_restart 0\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_Big( CVAR_SYSTEMINFO ) ); + cvar_modifiedFlags &= ~CVAR_SYSTEMINFO; + } + + if ( com_speeds->integer ) { + startTime = Sys_Milliseconds (); + } else { + startTime = 0; // quite a compiler warning + } + + // update ping based on the all received frames + SV_CalcPings(); + + if (com_dedicated->integer) SV_BotFrame( svs.time ); + + // run the game simulation in chunks + while ( sv.timeResidual >= frameMsec ) { + sv.timeResidual -= frameMsec; + svs.time += frameMsec; + + // let everything in the world think and move + VM_Call( gvm, GAME_RUN_FRAME, svs.time ); + } + + //rww - RAGDOLL_BEGIN + G2API_SetTime(svs.time,0); + //rww - RAGDOLL_END + + if ( com_speeds->integer ) { + time_game = Sys_Milliseconds () - startTime; + } + + // check timeouts + SV_CheckTimeouts(); + + // send messages back to the clients + SV_SendClientMessages(); + + SV_CheckCvars(); + + // send a heartbeat to the master if needed +#ifndef _XBOX // No master on Xbox + SV_MasterHeartbeat(); +#endif +} + +//============================================================================ + diff --git a/codemp/server/sv_net_chan.cpp b/codemp/server/sv_net_chan.cpp new file mode 100644 index 0000000..13427b9 --- /dev/null +++ b/codemp/server/sv_net_chan.cpp @@ -0,0 +1,169 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "server.h" + +// TTimo: unused, commenting out to make gcc happy +#if 1 +/* +============== +SV_Netchan_Encode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Encode( client_t *client, msg_t *msg ) { +#ifdef _XBOX + return; +#endif + long reliableAcknowledge, i, index; + byte key, *string; + int srdc, sbit, soob; + + if ( msg->cursize < SV_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = (qboolean)0; + + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = (qboolean)soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)client->lastClientCommandString; + index = 0; + // xor the client challenge with the netchan sequence number + key = client->challenge ^ client->netchan.outgoingSequence; + for (i = SV_ENCODE_START; i < msg->cursize; i++) { + // modify the key with the last received 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++; + // encode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} + +/* +============== +SV_Netchan_Decode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void SV_Netchan_Decode( client_t *client, msg_t *msg ) { +#ifdef _XBOX + return; +#endif + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit, soob; + byte key, *string; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + 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 *)client->reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // + key = client->challenge ^ serverId ^ messageAcknowledge; + for (i = msg->readcount + SV_DECODE_START; i < msg->cursize; i++) { + // modify the key with the last sent and 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++; + // decode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} +#endif + +/* +================= +SV_Netchan_TransmitNextFragment +================= +*/ +void SV_Netchan_TransmitNextFragment( netchan_t *chan ) { + Netchan_TransmitNextFragment( chan ); +} + + +/* +=============== +SV_Netchan_Transmit +================ +*/ + +//extern byte chksum[65536]; +void SV_Netchan_Transmit( client_t *client, msg_t *msg) { //int length, const byte *data ) { +// int i; + MSG_WriteByte( msg, svc_EOF ); +// for(i=SV_ENCODE_START;icursize;i++) { +// chksum[i-SV_ENCODE_START] = msg->data[i]; +// } +// Huff_Compress( msg, SV_ENCODE_START ); + SV_Netchan_Encode( client, msg ); + Netchan_Transmit( &client->netchan, msg->cursize, msg->data ); +} + +/* +================= +Netchan_SV_Process +================= +*/ +qboolean SV_Netchan_Process( client_t *client, msg_t *msg ) { + int ret; +// int i; + ret = Netchan_Process( &client->netchan, msg ); + if (!ret) + return qfalse; + SV_Netchan_Decode( client, msg ); +// Huff_Decompress( msg, SV_DECODE_START ); +// for(i=SV_DECODE_START+msg->readcount;icursize;i++) { +// if (msg->data[i] != chksum[i-(SV_DECODE_START+msg->readcount)]) { +// Com_Error(ERR_DROP,"bad\n"); +// } +// } + return qtrue; +} + diff --git a/codemp/server/sv_snapshot.cpp b/codemp/server/sv_snapshot.cpp new file mode 100644 index 0000000..21b61e0 --- /dev/null +++ b/codemp/server/sv_snapshot.cpp @@ -0,0 +1,833 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.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; + while ( newindex < to->num_entities || oldindex < from_num_entities ) { + if ( newindex >= to->num_entities ) { + 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_WriteDeltaEntity (msg, oldent, newent, qfalse ); + oldindex++; + newindex++; + continue; + } + + if ( newnum < oldnum ) { + // this is a new entity, send it from the baseline + MSG_WriteDeltaEntity (msg, &sv.svEntities[newnum].baseline, newent, qtrue ); + newindex++; + continue; + } + + if ( newnum > oldnum ) { + // the old entity isn't present in the new message + MSG_WriteDeltaEntity (msg, oldent, NULL, qtrue ); + 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 i; + 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); + + // NOTE, MRE: now sent at the start of every message from server to client + // 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, svs.time); + + // what we are delta'ing from + MSG_WriteByte (msg, lastframe); + + snapFlags = svs.snapFlagServerBit; + if ( client->rateDelayed ) { + snapFlags |= SNAPFLAG_RATE_DELAYED; + } + if ( client->state != CS_ACTIVE ) { + snapFlags |= SNAPFLAG_NOT_ACTIVE; + } + + 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 ) { +#ifdef _ONEBIT_COMBO + MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps, frame->pDeltaOneBit, frame->pDeltaNumBit ); +#else + MSG_WriteDeltaPlayerstate( msg, &oldframe->ps, &frame->ps ); +#endif + if (frame->ps.m_iVehicleNum) + { //then write the vehicle's playerstate too + if (!oldframe->ps.m_iVehicleNum) + { //if last frame didn't have vehicle, then the old vps isn't gonna delta + //properly (because our vps on the client could be anything) +#ifdef _ONEBIT_COMBO + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->vps, NULL, NULL, qtrue ); +#else + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->vps, qtrue ); +#endif + } + else + { +#ifdef _ONEBIT_COMBO + MSG_WriteDeltaPlayerstate( msg, &oldframe->vps, &frame->vps, frame->pDeltaOneBitVeh, frame->pDeltaNumBitVeh, qtrue ); +#else + MSG_WriteDeltaPlayerstate( msg, &oldframe->vps, &frame->vps, qtrue ); +#endif + } + } + } else { +#ifdef _ONEBIT_COMBO + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps, NULL, NULL ); +#else + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->ps ); +#endif + if (frame->ps.m_iVehicleNum) + { //then write the vehicle's playerstate too +#ifdef _ONEBIT_COMBO + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->vps, NULL, NULL, qtrue ); +#else + MSG_WriteDeltaPlayerstate( msg, NULL, &frame->vps, qtrue ); +#endif + } + } + + // delta encode the entities + SV_EmitPacketEntities (oldframe, frame, msg); + + // padding for rate debugging + if ( sv_padPackets->integer ) { + for ( i = 0 ; i < sv_padPackets->integer ; i++ ) { + MSG_WriteByte (msg, svc_nop); + } + } +} + + +/* +================== +SV_UpdateServerCommandsToClient + +(re)send all server commands the client hasn't acknowledged yet +================== +*/ +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) ] ); + } + client->reliableSent = client->reliableSequence; +} + +/* +============================================================================= + +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 QDECL 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, sharedEntity_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; + } + + eNums->snapshotEntities[ eNums->numSnapshotEntities ] = gEnt->s.number; + eNums->numSnapshotEntities++; +} + +/* +=============== +SV_AddEntitiesVisibleFromPoint +=============== +*/ +float g_svCullDist = -1.0f; +static void SV_AddEntitiesVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, + snapshotEntityNumbers_t *eNums, qboolean portal ) { + int e, i; + sharedEntity_t *ent; + svEntity_t *svEnt; + int l; + int clientarea, clientcluster; + int leafnum; + int c_fullsend; +#ifdef _XBOX + const byte *clientpvs; + const byte *bitvector; +#else + byte *clientpvs; + byte *bitvector; +#endif + vec3_t difference; + float length, radius; + + // 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; + + for ( e = 0 ; e < sv.num_entities ; e++ ) { + ent = SV_GentityNum(e); + + // never send entities that aren't linked in + if ( !ent->r.linked ) { + 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; + } + + // entities can be flagged to explicitly not be sent to the client + if ( ent->r.svFlags & SVF_NOCLIENT ) { + continue; + } + + // entities can be flagged to be sent to only one client + if ( ent->r.svFlags & SVF_SINGLECLIENT ) { + if ( ent->r.singleClient != frame->ps.clientNum ) { + continue; + } + } + // entities can be flagged to be sent to everyone but one client + if ( ent->r.svFlags & SVF_NOTSINGLECLIENT ) { + if ( ent->r.singleClient == frame->ps.clientNum ) { + 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->r.svFlags & SVF_BROADCAST || (e == frame->ps.clientNum) || (ent->r.broadcastClients[frame->ps.clientNum/32] & (1<<(frame->ps.clientNum%32)))) + { + 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 (com_RMG && com_RMG->integer) + { + VectorAdd(ent->r.absmax, ent->r.absmin, difference); + VectorScale(difference, 0.5f, difference); + VectorSubtract(origin, difference, difference); + length = VectorLength(difference); + + // calculate the diameter + VectorSubtract(ent->r.absmax, ent->r.absmin, difference); + radius = VectorLength(difference); + if (length-radius < /*sv_RMGDistanceCull->integer*/5000.0f) + { // more of a diameter check + SV_AddEntToSnapshot( svEnt, ent, eNums ); + } + } + else + { + // 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; + } + } + + if (g_svCullDist != -1.0f) + { //do a distance cull check + VectorAdd(ent->r.absmax, ent->r.absmin, difference); + VectorScale(difference, 0.5f, difference); + VectorSubtract(origin, difference, difference); + length = VectorLength(difference); + + // calculate the diameter + VectorSubtract(ent->r.absmax, ent->r.absmin, difference); + radius = VectorLength(difference); + if (length-radius >= g_svCullDist) + { //then don't add it + continue; + } + } + + // add it + SV_AddEntToSnapshot( svEnt, ent, eNums ); + + // if its a portal entity, add everything visible from its camera position + if ( ent->r.svFlags & SVF_PORTAL ) { + if ( ent->s.generic1 ) { + vec3_t dir; + VectorSubtract(ent->s.origin, origin, dir); + if ( VectorLengthSquared(dir) > (float) ent->s.generic1 * ent->s.generic1 ) { + continue; + } + } + 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 void SV_BuildClientSnapshot( client_t *client ) { + vec3_t org; + clientSnapshot_t *frame; + snapshotEntityNumbers_t entityNumbers; + int i; + sharedEntity_t *ent; + entityState_t *state; + svEntity_t *svEnt; + sharedEntity_t *clent; + playerState_t *ps; + + // 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; + Com_Memset( frame->areabits, 0, sizeof( frame->areabits ) ); + + frame->num_entities = 0; + + clent = client->gentity; + if ( !clent || client->state == CS_ZOMBIE ) { + return; + } + + // grab the current playerState_t + ps = SV_GameClientNum( client - svs.clients ); + frame->ps = *ps; +#ifdef _ONEBIT_COMBO + frame->pDeltaOneBit = &ps->deltaOneBits; + frame->pDeltaNumBit = &ps->deltaNumBits; +#endif + + if (ps->m_iVehicleNum) + { //get the vehicle's playerstate too then + sharedEntity_t *veh = SV_GentityNum(ps->m_iVehicleNum); + + if (veh && veh->playerState) + { //Now VMA it and we've got ourselves a playerState + playerState_t *vps = ((playerState_t *)VM_ArgPtr((int)veh->playerState)); + + frame->vps = *vps; +#ifdef _ONEBIT_COMBO + frame->pDeltaOneBitVeh = &vps->deltaOneBits; + frame->pDeltaNumBitVeh = &vps->deltaNumBits; +#endif + } + } + + int clientNum; + // never send client's own entity, because it can + // be regenerated from the playerstate + clientNum = frame->ps.clientNum; + if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); + } + svEnt = &sv.svEntities[ clientNum ]; + svEnt->snapshotCounter = sv.snapshotCounter; + + + // find the client's viewpoint + VectorCopy( ps->origin, org ); + org[2] += ps->viewheight; + + // add all the entities directly visible to the eye, which + // may include portal entities that merge other viewpoints + SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse ); + + // 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++; + // this should never hit, map should always be restarted first in SV_Frame + if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) { + Com_Error(ERR_FATAL, "svs.nextSnapshotEntities wrapped"); + } + frame->num_entities++; + } +} + + +/* +==================== +SV_RateMsec + +Return the number of msec a given size message is supposed +to take to clear, based on the current rate +==================== +*/ +#define HEADER_RATE_BYTES 48 // include our header, IP header, and some overhead +static int SV_RateMsec( client_t *client, int messageSize ) { + int rate; + int rateMsec; + + // individual messages will never be larger than fragment size + if ( messageSize > 1500 ) { + messageSize = 1500; + } + rate = client->rate; + if ( sv_maxRate->integer ) { + if ( sv_maxRate->integer < 1000 ) { + Cvar_Set( "sv_MaxRate", "1000" ); + } + if ( sv_maxRate->integer < rate ) { + rate = sv_maxRate->integer; + } + } + rateMsec = ( messageSize + HEADER_RATE_BYTES ) * 1000 / rate; + + return rateMsec; +} + +/* +======================= +SV_SendMessageToClient + +Called by SV_SendClientSnapshot and SV_SendClientGameState +======================= +*/ +void SV_SendMessageToClient( msg_t *msg, client_t *client ) { + int rateMsec; + + // MW - my attempt to fix illegible server message errors caused by + // packet fragmentation of initial snapshot. + while(client->state&&client->netchan.unsentFragments) + { + // send additional message fragments if the last message + // was too large to send at once + Com_Printf ("[ISM]SV_SendClientGameState() [1] for %s, writing out old fragments\n", client->name); + SV_Netchan_TransmitNextFragment(&client->netchan); + } + + // record information about the message + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg->cursize; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; + + // send the datagram + SV_Netchan_Transmit( client, msg ); //msg->cursize, msg->data ); + + // set nextSnapshotTime based on rate and requested number of updates + + // local clients get snapshots every frame + if ( client->netchan.remoteAddress.type == NA_LOOPBACK || Sys_IsLANAddress (client->netchan.remoteAddress) ) { + client->nextSnapshotTime = svs.time - 1; + return; + } + + // normal rate / snapshotMsec calculation + rateMsec = SV_RateMsec( client, msg->cursize ); + + if ( rateMsec < client->snapshotMsec ) { + // never send more packets than this, no matter what the rate is at + rateMsec = client->snapshotMsec; + client->rateDelayed = qfalse; + } else { + client->rateDelayed = qtrue; + } + + client->nextSnapshotTime = svs.time + rateMsec; + + // don't pile up empty snapshots while connecting + if ( client->state != CS_ACTIVE ) { + // a gigantic connection message may have already put the nextSnapshotTime + // more than a second away, so don't shorten it + // do shorten if client is downloading +#ifdef _XBOX // No downloads on Xbox + if ( client->nextSnapshotTime < svs.time + 1000 ) { +#else + if ( !*client->downloadName && client->nextSnapshotTime < svs.time + 1000 ) { +#endif + client->nextSnapshotTime = svs.time + 1000; + } + } +} + + +/* +======================= +SV_SendClientSnapshot + +Also called by SV_FinalMessage + +======================= +*/ +extern cvar_t *fs_gamedirvar; +void SV_SendClientSnapshot( client_t *client ) { + byte msg_buf[MAX_MSGLEN]; + msg_t msg; + + if (!client->sentGamedir) + { //rww - if this is the case then make sure there is an svc_setgame sent before this snap + int i = 0; + + MSG_Init (&msg, msg_buf, sizeof(msg_buf)); + + //have to include this for each message. + MSG_WriteLong( &msg, client->lastClientCommand ); + + MSG_WriteByte (&msg, svc_setgame); + + while (fs_gamedirvar->string[i]) + { + MSG_WriteByte(&msg, fs_gamedirvar->string[i]); + i++; + } + MSG_WriteByte(&msg, 0); + + // MW - my attempt to fix illegible server message errors caused by + // packet fragmentation of initial snapshot. + //rww - reusing this code here + while(client->state&&client->netchan.unsentFragments) + { + // send additional message fragments if the last message + // was too large to send at once + Com_Printf ("[ISM]SV_SendClientGameState() [1] for %s, writing out old fragments\n", client->name); + SV_Netchan_TransmitNextFragment(&client->netchan); + } + + // record information about the message + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSize = msg.cursize; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageSent = svs.time; + client->frames[client->netchan.outgoingSequence & PACKET_MASK].messageAcked = -1; + + // send the datagram + SV_Netchan_Transmit( client, &msg ); //msg->cursize, msg->data ); + + client->sentGamedir = qtrue; + } + + // 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->r.svFlags & SVF_BOT ) { + return; + } + + MSG_Init (&msg, msg_buf, sizeof(msg_buf)); + msg.allowoverflow = qtrue; + + // NOTE, MRE: all server->client messages now acknowledge + // let the client know which reliable clientCommands we have received + MSG_WriteLong( &msg, client->lastClientCommand ); + + // (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 ); + + // Add any download data if the client is downloading +#ifndef _XBOX // No downloads on Xbox + SV_WriteDownloadToClient( client, &msg ); +#endif + + // 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 < sv_maxclients->integer ; i++, c++) { + if (!c->state) { + continue; // not connected + } + + if ( svs.time < c->nextSnapshotTime ) { + continue; // not time yet + } + + // send additional message fragments if the last message + // was too large to send at once + if ( c->netchan.unsentFragments ) { + c->nextSnapshotTime = svs.time + + SV_RateMsec( c, c->netchan.unsentLength - c->netchan.unsentFragmentStart ); + SV_Netchan_TransmitNextFragment( &c->netchan ); + continue; + } + + // generate and send a new message + SV_SendClientSnapshot( c ); + } +} + diff --git a/codemp/server/sv_world.cpp b/codemp/server/sv_world.cpp new file mode 100644 index 0000000..f05fb1f --- /dev/null +++ b/codemp/server/sv_world.cpp @@ -0,0 +1,894 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +// world.c -- world query functions + +#include "server.h" +#include "../ghoul2/G2_local.h" +extern CMiniHeap *G2VertSpaceServer; + +/* +================ +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 sharedEntity_t *ent ) { + if ( ent->r.bmodel ) { + // explicit hulls in the BSP model + return CM_InlineModel( ent->s.modelindex ); + } + if ( ent->r.svFlags & SVF_CAPSULE ) { + // create a temp capsule from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qtrue ); + } + + // create a temp tree from bounding box sizes + return CM_TempBoxModel( ent->r.mins, ent->r.maxs, qfalse ); +} + + + +/* +=============================================================================== + +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 4 +#define AREA_NODES 64 + +worldSector_t sv_worldSectors[AREA_NODES]; +int sv_numworldSectors; + + +/* +=============== +SV_SectorList_f +=============== +*/ +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 ); + } +} + +/* +=============== +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; + + Com_Memset( sv_worldSectors, 0, sizeof(sv_worldSectors) ); + sv_numworldSectors = 0; + + // get world map bounds + h = CM_InlineModel( 0 ); + CM_ModelBounds( h, mins, maxs ); + SV_CreateworldSector( 0, mins, maxs ); +} + + +/* +=============== +SV_UnlinkEntity + +=============== +*/ +void SV_UnlinkEntity( sharedEntity_t *gEnt ) { + svEntity_t *ent; + svEntity_t *scan; + worldSector_t *ws; + + ent = SV_SvEntityForGentity( gEnt ); + + gEnt->r.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( sharedEntity_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; + + 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->r.bmodel ) { + gEnt->s.solid = SOLID_BMODEL; // a solid_box will never create this value + } else if ( gEnt->r.contents & ( CONTENTS_SOLID | CONTENTS_BODY ) ) { + // assume that x/y are equal and symetric + i = gEnt->r.maxs[0]; + if (i<1) + i = 1; + if (i>255) + i = 255; + + // z is not symetric + j = (-gEnt->r.mins[2]); + if (j<1) + j = 1; + if (j>255) + j = 255; + + // and z maxs can be negative... + k = (gEnt->r.maxs[2]+32); + if (k<1) + k = 1; + if (k>255) + k = 255; + + gEnt->s.solid = (k<<16) | (j<<8) | i; + + if (gEnt->s.solid == SOLID_BMODEL) + { //yikes, this would make everything explode violently. + gEnt->s.solid = (k<<16) | (j<<8) | i-1; + } + } + else + { + gEnt->s.solid = 0; + } + + // get the position + origin = gEnt->r.currentOrigin; + angles = gEnt->r.currentAngles; + + // set the abs box + if ( gEnt->r.bmodel && (angles[0] || angles[1] || angles[2]) ) { + // expand for rotation + float max; + int i; + + max = RadiusFromBounds( gEnt->r.mins, gEnt->r.maxs ); + for (i=0 ; i<3 ; i++) { + gEnt->r.absmin[i] = origin[i] - max; + gEnt->r.absmax[i] = origin[i] + max; + } + } else { + // normal + VectorAdd (origin, gEnt->r.mins, gEnt->r.absmin); + VectorAdd (origin, gEnt->r.maxs, gEnt->r.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->r.absmin[0] -= 1; + gEnt->r.absmin[1] -= 1; + gEnt->r.absmin[2] -= 1; + gEnt->r.absmax[0] += 1; + gEnt->r.absmax[1] += 1; + gEnt->r.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->r.absmin, gEnt->r.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->r.absmin[0], gEnt->r.absmin[1], gEnt->r.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 ); + } + + gEnt->r.linkcount++; + + // find the first world sector node that the ent's box crosses + node = sv_worldSectors; + while (1) + { + if (node->axis == -1) + break; + if ( gEnt->r.absmin[node->axis] > node->dist) + node = node->children[0]; + else if ( gEnt->r.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->r.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; + int *list; + int count, maxcount; +} areaParms_t; + + +/* +==================== +SV_AreaEntities_r + +==================== +*/ +void SV_AreaEntities_r( worldSector_t *node, areaParms_t *ap ) { + svEntity_t *check, *next; + sharedEntity_t *gcheck; + int count; + + count = 0; + + for ( check = node->entities ; check ; check = next ) { + next = check->nextEntityInWorldSector; + + gcheck = SV_GEntityForSvEntity( check ); + + if ( gcheck->r.absmin[0] > ap->maxs[0] + || gcheck->r.absmin[1] > ap->maxs[1] + || gcheck->r.absmin[2] > ap->maxs[2] + || gcheck->r.absmax[0] < ap->mins[0] + || gcheck->r.absmax[1] < ap->mins[1] + || gcheck->r.absmax[2] < ap->mins[2]) { + continue; + } + + if ( ap->count == ap->maxcount ) { + Com_DPrintf ("SV_AreaEntities: MAXCOUNT\n"); + return; + } + + ap->list[ap->count] = check - sv.svEntities; + 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, int *entityList, int maxcount ) { + areaParms_t ap; + + ap.mins = mins; + ap.maxs = maxs; + ap.list = entityList; + ap.count = 0; + ap.maxcount = maxcount; + + SV_AreaEntities_r( sv_worldSectors, &ap ); + + return ap.count; +} + + + +//=========================================================================== + + +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; + + vec3_t end; + + int passEntityNum; + int contentmask; + int capsule; + + int traceFlags; + int useLod; + trace_t trace; // make sure nothing goes under here for Ghoul2 collision purposes +/* +Ghoul2 Insert End +*/ +} moveclip_t; + + +/* +==================== +SV_ClipToEntity + +==================== +*/ +void SV_ClipToEntity( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask, int capsule ) { + sharedEntity_t *touch; + clipHandle_t clipHandle; + float *origin, *angles; + + touch = SV_GentityNum( entityNum ); + + Com_Memset(trace, 0, sizeof(trace_t)); + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( contentmask & touch->r.contents ) ) { + trace->fraction = 1.0; + return; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace ( trace, (float *)start, (float *)end, + (float *)mins, (float *)maxs, clipHandle, contentmask, + origin, angles, capsule); + + if ( trace->fraction < 1 ) { + trace->entityNum = touch->s.number; + } +} + + +/* +==================== +SV_ClipMoveToEntities + +==================== +*/ +#ifndef FINAL_BUILD +static float VectorDistance(vec3_t p1, vec3_t p2) +{ + vec3_t dir; + + VectorSubtract(p2, p1, dir); + return VectorLength(dir); +} +#endif +#pragma warning(disable : 4701) //local variable used without having been init +static void SV_ClipMoveToEntities( moveclip_t *clip ) { + static int touchlist[MAX_GENTITIES]; + int i, num; + sharedEntity_t *touch; + int passOwnerNum; + trace_t trace, oldTrace= {0}; + clipHandle_t clipHandle; + float *origin, *angles; + int thisOwnerShared = 1; + + num = SV_AreaEntities( clip->boxmins, clip->boxmaxs, touchlist, MAX_GENTITIES); + + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + passOwnerNum = ( SV_GentityNum( clip->passEntityNum ) )->r.ownerNum; + if ( passOwnerNum == ENTITYNUM_NONE ) { + passOwnerNum = -1; + } + } else { + passOwnerNum = -1; + } + + if ( SV_GentityNum(clip->passEntityNum)->r.svFlags & SVF_OWNERNOTSHARED ) + { + thisOwnerShared = 0; + } + + for ( i=0 ; itrace.allsolid ) { + return; + } + touch = SV_GentityNum( touchlist[i] ); + + // see if we should ignore this entity + if ( clip->passEntityNum != ENTITYNUM_NONE ) { + if ( touchlist[i] == clip->passEntityNum ) { + continue; // don't clip against the pass entity + } + if ( touch->r.ownerNum == clip->passEntityNum) { + if (touch->r.svFlags & SVF_OWNERNOTSHARED) + { + if ( clip->contentmask != (MASK_SHOT | CONTENTS_LIGHTSABER) && + clip->contentmask != (MASK_SHOT)) + { //it's not a laser hitting the other "missile", don't care then + continue; + } + } + else + { + continue; // don't clip against own missiles + } + } + if ( touch->r.ownerNum == passOwnerNum && + !(touch->r.svFlags & SVF_OWNERNOTSHARED) && + thisOwnerShared ) { + continue; // don't clip against other missiles from our owner + } + + if (touch->s.eType == ET_MISSILE && + !(touch->r.svFlags & SVF_OWNERNOTSHARED) && + touch->r.ownerNum == passOwnerNum) + { //blah, hack + continue; + } + } + + // if it doesn't have any brushes of a type we + // are looking for, ignore it + if ( ! ( clip->contentmask & touch->r.contents ) ) { + continue; + } + + if ((clip->contentmask == (MASK_SHOT|CONTENTS_LIGHTSABER) || clip->contentmask == MASK_SHOT) && (touch->r.contents > 0 && (touch->r.contents & CONTENTS_NOSHOT))) + { + continue; + } + + // might intersect, so do an exact clip + clipHandle = SV_ClipHandleForEntity (touch); + + origin = touch->r.currentOrigin; + angles = touch->r.currentAngles; + + + if ( !touch->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + CM_TransformedBoxTrace ( &trace, (float *)clip->start, (float *)clip->end, + (float *)clip->mins, (float *)clip->maxs, clipHandle, clip->contentmask, + origin, angles, clip->capsule); + + + if (clip->traceFlags & G2TRFLAG_DOGHOULTRACE) + { // keep these older variables around for a bit, incase we need to replace them in the Ghoul2 Collision check + oldTrace = clip->trace; + } + + if ( trace.allsolid ) { + clip->trace.allsolid = qtrue; + trace.entityNum = touch->s.number; + } else if ( trace.startsolid ) { + clip->trace.startsolid = qtrue; + trace.entityNum = touch->s.number; + + //rww - added this because we want to get the number of an ent even if our trace starts inside it. + clip->trace.entityNum = touch->s.number; + } + + if ( trace.fraction < clip->trace.fraction ) { + byte 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 = (qboolean)((unsigned)clip->trace.startsolid | (unsigned)oldStart); + } +/* +Ghoul2 Insert Start +*/ +#if 0 + // decide if we should do the ghoul2 collision detection right here + if ((trace.entityNum == touch->s.number) && (clip->traceFlags)) + { + // do we actually have a ghoul2 model here? + if (touch->s.ghoul2) + { + 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++; + } + } + + G2API_CollisionDetect(&clip->trace.G2CollisionMap[0], *((CGhoul2Info_v *)touch->s.ghoul2), + touch->s.angles, touch->s.origin, svs.time, touch->s.number, clip->start, clip->end, touch->s.modelScale, G2VertSpaceServer, clip->traceFlags, clip->useLod); + + // 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 + //rww - since this is multiplayer and we don't have the luxury of violating networking rules in horrible ways, + //this must be done somewhat differently. + if ((clip->traceFlags & G2TRFLAG_DOGHOULTRACE) && trace.entityNum == touch->s.number && touch->ghoul2 && ((clip->traceFlags & G2TRFLAG_HITCORPSES) || !(touch->s.eFlags & EF_DEAD))) + { //standard behavior will be to ignore g2 col on dead ents, but if traceFlags is set to allow, then we'll try g2 col on EF_DEAD people too. + static G2Trace_t G2Trace; + vec3_t angles; + float fRadius = 0.0f; + int tN = 0; + int bestTr = -1; + + if (clip->mins[0] || + clip->maxs[0]) + { + fRadius=(clip->maxs[0]-clip->mins[0])/2.0f; + } + + if (clip->traceFlags & G2TRFLAG_THICK) + { //if using this flag, make sure it's at least 1.0f + if (fRadius < 1.0f) + { + fRadius = 1.0f; + } + } + + memset (&G2Trace, 0, sizeof(G2Trace)); + while (tN < MAX_G2_COLLISIONS) + { + G2Trace[tN].mEntityNum = -1; + tN++; + } + + if (touch->s.number < MAX_CLIENTS) + { + VectorCopy(touch->s.apos.trBase, angles); + } + else + { + VectorCopy(touch->r.currentAngles, angles); + } + angles[ROLL] = angles[PITCH] = 0; + + //I would think that you could trace from trace.endpos instead of clip->start, but that causes it to miss sometimes.. Not sure what it's off, but if it could be done like that, it would probably + //be faster. +#ifndef FINAL_BUILD + if (sv_showghoultraces->integer) + { + Com_Printf( "Ghoul2 trace lod=%1d length=%6.0f to %s\n",clip->useLod,VectorDistance(clip->start, clip->end),(*((CGhoul2Info_v *)touch->ghoul2))[0].mFileName); + } +#endif + G2API_CollisionDetect(G2Trace, *((CGhoul2Info_v *)touch->ghoul2), angles, touch->r.currentOrigin, svs.time, touch->s.number, clip->start, clip->end, touch->modelScale, G2VertSpaceServer, 0, clip->useLod, fRadius); + + + tN = 0; + while (tN < MAX_G2_COLLISIONS) + { + if (G2Trace[tN].mEntityNum == touch->s.number) + { //ok, valid + bestTr = tN; + break; + } + else if (G2Trace[tN].mEntityNum == -1) + { //there should not be any after the first -1 + break; + } + tN++; + } + + if (bestTr == -1) + { //Well then, put the trace back to the old one. + clip->trace = oldTrace; + } + else + { //Otherwise, set the endpos/normal/etc. to the model location hit instead of leaving it out in space. + VectorCopy(G2Trace[bestTr].mCollisionPosition, clip->trace.endpos); + VectorCopy(G2Trace[bestTr].mCollisionNormal, clip->trace.plane.normal); + + if (clip->traceFlags & G2TRFLAG_GETSURFINDEX) + { //we have requested that surfaceFlags be stomped over with the g2 hit surface index. + if (clip->trace.entityNum == G2Trace[bestTr].mEntityNum) + { + clip->trace.surfaceFlags = G2Trace[bestTr].mSurfaceIndex; + } + } + } + } +#endif +/* +Ghoul2 Insert End +*/ + } +} +#pragma warning(default : 4701) //local variable used without having been init + +/* +================== +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, int passEntityNum, int contentmask, int capsule, int traceFlags, int useLod ) { +/* +Ghoul2 Insert End +*/ + moveclip_t clip; + int i; + + if ( !mins ) { + mins = vec3_origin; + } + if ( !maxs ) { + maxs = vec3_origin; + } + + Com_Memset ( &clip, 0, sizeof ( moveclip_t ) ); + + // clip to world + CM_BoxTrace( &clip.trace, start, end, mins, maxs, 0, contentmask, capsule ); + clip.trace.entityNum = clip.trace.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + if ( clip.trace.fraction == 0 ) { + *results = clip.trace; + return; // blocked immediately by the world + } + + clip.contentmask = contentmask; +/* +Ghoul2 Insert Start +*/ + VectorCopy( start, clip.start ); + clip.traceFlags = traceFlags; + clip.useLod = useLod; +/* +Ghoul2 Insert End +*/ +// VectorCopy( clip.trace.endpos, clip.end ); + VectorCopy( end, clip.end ); + clip.mins = mins; + clip.maxs = maxs; + clip.passEntityNum = passEntityNum; + clip.capsule = capsule; + + // 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 + 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 ); + + *results = clip.trace; +} + + + +/* +============= +SV_PointContents +============= +*/ +int SV_PointContents( const vec3_t p, int passEntityNum ) { + int touch[MAX_GENTITIES]; + sharedEntity_t *hit; + int i, num; + int contents, c2; + clipHandle_t clipHandle; + float *angles; + + // 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.angles; + if ( !hit->r.bmodel ) { + angles = vec3_origin; // boxes don't rotate + } + + c2 = CM_TransformedPointContents (p, clipHandle, hit->s.origin, hit->s.angles); + + contents |= c2; + } + + return contents; +} + + diff --git a/codemp/server/vssver.scc b/codemp/server/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..a124f85d9342314ed7132b532f472b9118542fb6 GIT binary patch literal 208 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiY`dwgAYh>-yd4g={gyXxz^dn;pjfg(qM{HVB; zJy&P4#;^hTM}hn>@9xb1Lh>=(K>jfxfAg268VjEr#fSj;$ANra!!3GKT`t611NkR_ z{EqpJJD*lRjh6uOPXhU(Iu`FuGk?YN0r{tZe7^@G@8?c$jAsGz&j9)IMjop-I!*%1 gp9S)lg`Ga6KXDOQzlR_L1IOm-*4^_`Vt`T%05`Tjk^lez literal 0 HcmV?d00001 diff --git a/codemp/smartheap/HA312W32.DLL b/codemp/smartheap/HA312W32.DLL new file mode 100644 index 0000000000000000000000000000000000000000..2b1ea82afed700e0a470d611edc9c044016ab2c0 GIT binary patch literal 382464 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

9q^0& z#=TSv784s?*l)6-@T+i(*o3m+E&hIG6Kq$bmpV8SPNRx!nvYw==IjlVwL#mXRBtch zZ#P-#s;Ry8ermo`hzNLzshX)w#gWw*Vsv?F0$+5jwM3q(oWWJySPU{aeTkrD3!jHh z!z7)u!!@f&7Vjf>#u}hDcnNN2(a%mxsfj#*hc5_PF>eLmIkT2)=gxO!p%#G$K*7#B z(Jfj(;Qt9cU<`N!^(vI@0kB^61-gV_kR-)4Pw0oXp*C^;HPWUGz=CgYeG}Ddw+gfn9hW>r6{OIFf2A7*$|JP#ZHvi!={sbV z_tNG!I%3yxFLomv>4zR{V_xcD8wj%@;%$VSes%v3893c8pY-#cWTS)lE+jn|-Y0AE zzal zE27Wwv@((=8HaNli3;=^1$%{*IiYJKKOiNZuF04jP_A)mWFsk9z~YWSp2l>D8D8`n zFuVDIj|j8n36ji|AE3$>B*<}`U!5ah(UxohN)ld^QWD@A&PT=kHtP3NzHTb4Av|g= z3C&zwYhcALjJ*k~T4OA@s(*Z>mgB!bVpr7>5>xAVrf3)9AYt{qhnL#fr|$4{J^?Wo z_uqRM2o9^Hp2FaOsK++OB2_0ms_jv6xYO112r5uGw2!jnsKNJXE+Y*KUFZg=_s`{R z;AqSb-e@Agmb~PsQWSMWmU_ffrout&M9`17P-J$bjUW;BMzEJbJD`8S@I&+kKTOg4 zG+;J7!oVOHwNs1k6ufKFj_jT{0kcwdq<=i4{?-7j|CNpx)V`%v3;+BMuG`YN+t+Dme_Db&;usd|IFMFNnfB|H!? z6M?Bq_*RP4UjIfw@wt=(;>-l{MBvfD3NyJYDXgNjdiM@vn8h?B1GDnQkUp{xm;@dsT#sq@D+6Lv`A>a9jJ zm$mS;{Xa>^M$*x&kWK1?a}KG`9GVcF*jpWI4mp)2a2KtPEh9X_`W90AoesQ%tb9RT zgIabazXz#i-Lwgq`XC#KcX;fL!egO|YfWH?|&1eKUL- z`T5o$!W>vmid&+V=EW|@$u1~t5Y_?Dt|SgO)OtYLYhK%QZ>ZQ!Lh0(&R@X~?>eGJ_ zy@4W<{fS1NBHkuU8{vm{t52@qhVv2}Gn=%E3@9}6v*tKIW7f4oo{l0W={U}Guu-$O zmO!IP;^Y#@-l6ez%**`hOKh6Xb2k?miee>A5Ylt-SkmOMw_2!Bm93--!BG1!ljWvk za0Ss-)lIW{5_YX})nV+!R5K{o!1c!cQ0IDd)TuNb>8jd^0Mt!tdv zTeMzs?=C;<&DrJO6Zp%C_mFTUHFbk^eG3XQeFcb2!wT>^#z8~QjXz-IM6uO7riDC& zeK5}HPApp?CCu!4i3TKj4>Jo~2&gPp6skeeHM_ zrv*ROSd}(p`clE8_to%!xlhe_U*Mm(i4A;Ka9(*yt5+^!a^_(W8l^@P$ec|4wlSwc zdyfCO(SiijIHWEIj((tP$3Y7zwvi`1T^n=18qFg} zG{58#r#N7HK`4fBiU$)cU>HkW#@Y*G?zU~ycVu@}l7kvkbTS^$k}2wJ-rzLhhZR5I zs;bfkmWP8KYR}$AdZ?mycpk5wf)ha1hBJH&Ce9b>u%$YWju96--D0=A zsE>zI@#o8Rr?ulBsk2b|BKi2I|3Cv?zzGOtocvX~0ho-VpGvSC9A?f&@Q7jrkC+Pv zBv;PovI9wE+pX>$*(>q62cIxL^qW5V64ZbGCj3RGme`AZ>cMw)+R>;6cjX(e@~hL> z6oo0^X&`+UnbXUeN)9U+7F%|;<99iN4=FjHQ_5o7u0DKEH(}W1YKM}upWix_u?OhE zpUc`nxq6mRqHSJ0cgt8(VsG}Bv9fe?^_5D_X1tIW7b|18(u04tRwd{|GV^bu@YAExmrAc+G zP=)b^Nv~IfpCbHWf^w1;DO*s0QU2H#mp|g7$80*|1y2u=Q3BYaj6H@`u02N91jkSx zHd{w&4zj?4c}@E)FG(T2a`?3R>n%w##S8ZdnWD4jB4p7v76wt`@MdNOsAh6k6|qE< zwxG#c!K(c|pQAOY=@^Vjb{&BoFBa|SH~D~%Z+>9G^Qb$*R1N4bRDs{x-I2@qu=*V8 z#kxFG_;RG^_L)71)JI}UCf-TykW>yR_}@fTQ*gDaV_5t{VvWcH&;(cTWd05jr9-H@ zI=J?06F90phFH#wGql!Kb%+X)ZOmeh2}1Pyu->;i+!$wgkxty;(JtvHu~xOPmb zJtC)3KSR^eB=Ix6g#FJZ1{D~ZO0I;kJN@=H#;Gm})X#({a6m>bvT(quVdy*n=M=3$?>`YY3xS1(eb@!UmLzVcI@dR=} zs*{S-!fofMy7a68L_Au;nLi2X*#25ND z1vYCZ&>vM&7$c8q?-447loVK#`3>at?~4q~87N6SbY)Nh!VX@M`~waz?8jP;6Gr?E zk>@<<#AiS6YP%laEAWmc!!bF06jMS(zxFgb)LZ`m`1T`$Gu0O=W$qZMKkAdKngxT5 zv{5~{6SY}w;Dc5$PU?$S&1J|eqG&d= z*Lr<)ZgGa~4oM=pR|fc{@7nKa!8iIq^9<6e*k2>aM_g=zzut6gfYu80U|4}S-cEgB zy?`Nqqa`U*Rq9M>Nts+JGbyoYg*&nz(>@3A{jv5;9C(*$2h(Ln&kHa|={VH$1->Xj z`$u{awFcT9jo|tB1<-bCzvKL!IQ%Pkza+6XQ5D+!pki2<*k=y*-_Sv8HwIFscR{&t zNOwZpCd$JI#O4KCfseA+IZv{&^kD5hh?5LMeXe+YFt5o)eNIH}pzCCM-EVRJJf2^I zezo>N!;^kQb?M8St}o$q^%YJ_THliJ^XMZ~cJTD?Jeo2c^C!`MDa---wV#we0-LrLI{nf1 zl<5QT`}GdWH#q(;?@UUMn0qG!5 z3rsGoKRdbst{ENDolaBOvO6uD$>KzZm*a$gbR922MeQB&_{D5SpAc;QYDXjIq0RRK zEN;Sd&E7T_&j`VohbV}-@@k9p9Gw?f>{};O9;KS@ek=~Ob$G}#7lHR3&&a3X+sUm1+J;LmCab(&;S+??#HEJxjZI!LPcD-){t)W=y4zdA*9 zS%e?$NIJ;U-VyHV_$v6<>a;;fiYP7OzZdlio92tSK9-Tij@ItG*M|#yKS7(Jt-qH;#r3(Bad- zTdIq`EFoy2Arb%4Q7_+JPQK?3V^o6a*i|>$8q}?Nb@o<*7^+MG zJ05@Ocpk#Hl0sBC|3vI+cRl{(mW15JVOMY_QUs;-OOT3>Bhna zbve^8D{y{|`T{K*ScCQ$p3w%~f5-izU^>*Y)9C_4V?Ioc5hXO&qn@8Axe0^^Vur%3 zd}Kg55Fla;fYx5Jx2;4Ho!SLZC@)Z!WwJ{*Xt@)dPML*x>e#2CpTz6(sZ7h=`M=z2 zZ_A@+1lrCsg=X`2dZ-Y9xd4|bIupT?XQ$cQZp7AvW&vpfBQ+HFoit=UY4}Nbxf6It zxvgNBp&YTV!$v{cpVuMXWnE=&dlwa=9hb@83ezAH-iL}i5Iu!nz;4%@{H+rH-T`OOEL1#DwczK|e$g&>=l3>g&I zuHBc|TWLmtANhLsTf((UjV5qBQyG$s6abC+KnH>rEB zN$NY3XV^z;r#epRTe!A{ZVosr_~t-&`3?-C%N4q`u5kj+*HJXiL5M1l22Txjjk$jH zmi}oN#o5wP3x?bsF6phA<_K4awnpx$_@2Qj@hv1WVQHjH%- zo#}Bwt9(~^&pzm}kk4%jPhnskQeUlo)7O!a0B#ikFUJ>8ZgaSjLOb(HhV7yBqJ!?i zJ_`jbvxcx${$_d4w^5~1WfC^g1y(Z;ocR^$)El8?J&Q$qG>yygh>KT2O`H7DQn<^6PuU-u6BcfSqD#4~tt( zBQzEPWt`49cB1Ra+~Sti1iEG~^)GRYNX?l#(3}(ELLvAX2FL}R5Wldu5*;de~9dh@QH!nmH;!5{kyx=Oa-G}dMZ2?o z_~{}iDW{0Y_vdK~)Y->zhk&Y|O$_|a803()k3+d)ppr)j;okEYOVe?y6$r9ReV2y} z<*;GKmP;EjTI-42XuV&K^KrxPsD*xrS=u(A($J&2uFCnqA*|;tP>S!t6=9Qfn7q_A z)G)RaYq}EKUb-Xu34HYL{?EqWdLH^dgpVn0Wc**c8IP8cBQr8bX2o9)e6Wrjd5(CS zJ#vJ2u#FsK{a;wESuAG8|47Ge zI1a*n0r2~JEQKSnmvPa3vyz+L`JttB{E026f43#!rOHOrsR zhi3WX9y(J|YMha4XGZ*Izzexv`5NqpIT(F|YBg3qgDS@zh=4Mx_XWs{Zvhkv4vFb( z$%d@cMO3if4W}R3xMg6mxBfr8paM!odI^MG`=%e`MJbvIj|(VPSfPe444m9|Iu<_v zY3f#V+Um*TeRwQwQyq|iPsh;fS)7b$e+Pkv6;xx%g3RzuN<}7ByVm!wRyj&knGP4` zH=geK9YGBo(#xWcCs9tZVf8!Na6!ZXYhm6 zRn)$vIu^-m!w{LGqIx0$2|rnYgc9K34Llnm7p`LVNY)75k-}Mllhv+rYj|-TZqLxc zFvPTTVZVn~k+rya@ic3JO5m8Ip2l%oqcKZxBAZc6wspYt zE


!%(9Q=f5bAnvHzk*0FZpj_gM&an}*wtMD;_F6sA`_*SkH`+ZwO0J#&XEsl;g1k=cBA z?2Fa$d<^RZ!0Plgj=-E#a8h*dJOj9>>(C*jTQG;E_L(d}_%UA`4fTrG39Ps=VVtG= z?3?JCSLv(vjiiLaoTJSi0SYn0-u836!`uldp9bLP3*@c55Wp%KD4PXSiSp)1YjjLG63-%f>)sBqthFrHvWa0@hgTZ>}@2UD6f`a8pH4n z#mHQv;!MHelIv&=;*i9IC#QWQDY_8CH3`OZxS5For6T}ivJilc23r0CAf+#7P5QVB zrji}$p)uN9%*`)nFk!Lwe|DqvzH>6c&`w%%>yO}}!oF<&_GkvbiI^-|Wn z@Fu;S@x2)P{e6My*zT9nZodF8o8QlBK9yycrs7#aGRJ?R%`?<8KGsZWYMEtAaV(Rp zmdVubs2V*yWg1l^(aacoc*3G1Y=p$JP`(gn^H%ICgx=%-FlDiXMCTx!(-go#&jon? zRC}Hpsm_m1#EV_P6D5ws^H{7B^bERv9Fw9>H3*@C8 zq@kp5UgM!pobR^LpgZcNu9?d9PUU1NBxw7lJwO8%`CPm7D?9~gI5FCETjSM`6)PzA zQ_)PZlA8aw^rZcsW@IbG`6gbS0+BHz@+yNLm&#Ay3UBt}#BbQZn_@ zd%-h8Wu>34Eh&BbjyW{((Wy^M-(Gu9>1TIL4t^uvsjPUXV1Gi=U`M1noxio8+Lw+$ z-1!VBw--_;BMb6NUs*e)WSw)(gh0V-N>{w@y>Pc7p&Pesl z(tT^cjbUyyFyqef%>kt(pq$j%aF>yZObU?UGcjU%J%E)=BMzKLoEJ(mOG)X0wb`YI zf!y+c=)L9kaYLOK4Wg6aF+=JALR7vZKev z;Bof7ql#XrFt@{NT==s%k0E@#EX+nZ_CpIn% z9w7B^4)ZQn$ZKN^IUjC@?5z)?pkk&)CxR!B+L497vRVBmHViZoRfFMC1FNW#YQb6u zz!5fS45CH(a6FpUXsTBigXEQJ|#LmO}HmkogC zqCe0W&`EDYqlF^*G(+eTy%_)}Rjt7XggP29=DGE_BrzUzGbRCMSe#L4qcS=8SRByM zl{y6al%zpzP;DBtM)Y@oZ9rb&6{jjXtib#m%#dB|Txl>NW)1Bt~el+ z%y?YrjIV&0xr}i^yYv?qpLLYy4oPCJ;J$cgep;pj7~gtYI+QGyi9riik9u1MsEwpe zD8pxbNx72;D$dctrs*_r~V(WOD^nKA(|n}2()kozRgO|Dsp2W z-5!t~P~@%mVJu;OEAZnc&f2_Sd>Z?{Q_8J!mH|OZCm{o%875p}EP)ncX5ldyYbS7N z$54-8Ou{k_uQXbuQy4*u_s!L@5~szBP7mz*KbH5)Mp!ZqZk>7(b%GFia5JDAl`{=c z6DSx~Yv>#XhA2~zzDA$KFF>Nk!5o8*B0(%M~(mEQ1di7c!I(-nV+923c3_^SaK#!1v(SJI>0h;q6 zj6*%7+}npYV5qtEesuxQQz9N!&@&C8vX_jX36}-0ot*s~@yacjHOk!kfUxU}YsQ5a z&b7eP3uvX}T1R+pbUsa(QfR8fvy=uaP?ob18GJlC}M(buZ6S3F3*it3+vHD zp&uhr2r`8R5TMS6-B1DgCGB@BeaPy_Sd)$Z1%puo?@w zXVbOxd=gb$_4VFHOT9<^Ei)FEqGIA80~ALql}8wf??8owEgu|y#CUuNk;e>({MBhf z^~X?3BUfV@@-;BRA=-vNFyyN;DPKMfEv8A{NF|EeVdDq1piUF3mmKKpETs!*qN$rt z(-wV1(DowHyhnW$S$TUJc2Vz5A-lp=$yFwqp-8m01!DT<+HokEhY>K#wHB0c!@M1D z+;S~U-a=Vu$t)vwEHx53Cx8^VFsY@vi`S(0L<`e8tHzs@dyTXbAzF!8?k7WDlbo-- zs1G8cy$&WOv6#PrS9#FlnNNRp@>PsvZsZ#=69HV zU2+I|oOk_~VD;oZI6PnQvDfP%7?@Pt&BDR}Kk|l^`p8Wh2MUcu{wH+;aA+%+v|oUu zv=BIG7M|1Wty}O+2v@c}4iE$&SK|e1ydutR*vIE_dM=jidChl?vnWwzQg|es271&j zz0BX>`3dI1>doR;bJMu=aHjqFT4&Kd@RO5m??fyQXao}8E3L#a0V;IFpiRQaK1OYM zR1-@XWZvS&3n#|wHw-1CmYFrJHjX&2qX9rh8?V6_LMR?0TQ`!X2--S_QUb~fH>g{* zol^In6Oh15yI0?$bOcRw=u8qh|1oy}&#&p{intsO&`OGN)HfN#Cpi1(b2Pjr2G-K? zbS@AVsXT@r{w-oDkKEjq*F(4rJaZz3Va>zc6y@a{YD}iRz6X;FvOCYM-VH_A_(sFU6#`K*0_T6NOUM{a4?9iIsX6P<`1 zPKVTY8d^dKQ8l=nCoi+edx>`AQj<|afUZch?XLr~gJ4T0&h_Dhwg6^YhW_U0{Y}mh zK_9^#-57M22ixm$=QAUe$_{&BOsxs)lqcsHUPSUXDi-EaAb2Nej+`HUgGdb1&zwPJ zXeN|^!L9b;b*_>R`RH=ca9VgKp%XVS=ziC70L$zaw_@>@*09pPHFnUwY?eAK%D0H? z9|Upd5^-w{g?Hnbn0F@V+94ZnDH;%D6V_?8UqPREzw9I~sdOwt(MgJ3>5{rEfZ@^Ps~_tbMsKiQY&A36S6@3xY$2niZ4T2>Jf4htT-ohubzV2^x{ zR=+PZ?DuS>w>N{Yr`Ol1mGhapS&OIVUT0kuny8o}w)Ti~bHwH=I8f5k4UWWp)OIiD z22TN?U^ng7mR+h($~gU4fJkcDU;sk*Y~Ez04OWrR1(d3k7=eUymF8TMwGZrj zKcl2&=L$4N{S9Yun?UoqK|+ma?-QPpwB9GRCw-hDIt$7pJ$a`&r?N6qMT+5EPiJK; zyVGn^(v$-cXM4nQA10jgMZvy|Bj^~`x9@z72GqvOkmj$3Dl6zQ|4}MFKRjFd(q-Rx z2}bjI>%51u4cV~r?GtO>7jds)d#0lK=%vz^6YU$n#Lz#l0!?QtgPYJMb2sa@bES0% zDQ~Zbzq3jjH5fv~6Xu&j3%TOQsp9;hf$5k4H&fEb6Fz&KI#Uk^Y4g463G~8HrLv$L zqDTpP8JgYlC3*R=ZzNMhZ*T!BuTw8w#+cC|AfQO!tJM%K;eo`xteo{1Nqr;Xr=o9k z*iC5v!V~-fG;4M0m|F(mmhegi`_ac!6>(~O5rA;j3qT`xvZ2s_(<_stj*$9n_DzrD z7a16=TEZ3)EASr*`0_FR-nsem?80X zgJw(YCpW|2B0i<)iOG~M{-xm`<~Qc`UaVgx+5y!|v3X5#DGVI0G0l%b^r~xFYeF(w z#^Sc#4aacn`d-p8pmir7?d2*sF8M>G@>JE9bNC`1)vZaxV*iA{ohbDhuo?EeDv3TGq0O%(Q1zw3eGJj0kk2z^moL8^B z*MK$5=_i)y2_6L)!DILnsr6trAp;?DNn<`yz`Gh#82ekLst^}0^1WE?iZM#kerG|S z`Gitc44zgh#TKo(3`J+d_*C%BuF-{NGONM0b4&!gl$ zoV>@Fe2qwUQ}O{$-eE}YSW?i{nQ4ma0We@L*bvfsT8D)pKY(??9{CW;GUD{_;u|Z# z0$8TjsTHi!0oA}N4)2kB_NowRm1d)o*$IA(Id6(H|2&MSV8&l79KihZUT_i3T?2l* zfcA&Qig7|^Fm)w!+Li8E=08GObkDfPeGQ$}>?B>=diOOZ7*GHp7=zH)iVzut%)S-& z0{FTuWbHKPS3;2BA`fUq*4a0H4UZVW1u!jGYbPFYEXX|K*{|aWp*IftPYW1yeHIef zAQCtFY3donRk%IDKF~v*df|;s%{_gzMpn6%S{LrM~jINEFcEj!+e@<+wdQZ*V*3x>2ipx?VT5qPVu>3qH=trCM?( zC+B*CPx5Owzqa*!0xZ(LBY1>g=V-6H>9wIrH?TxaKFhg2x}Ff>?D=ml3@lQU4m5d) zlMiUg_lvp(o{nr^ zlU$;W)10(WOLB4&pd*Z0R@3-iq$QogB;etv3LTs@MoW5^lSs4~>j~y@QeQ(F?d@iP z5y2O1jtejM&>tc)ke5jwv8CLyxe~FP07ssO%y9~X`N)8ckopV?r_s6oen-zny;;JS z6Zmp5z&}71Lg%f?$#9H`i&sRTgxXo0^;?|P)lp02BNsgY$dJ^?*JttkR zC8dibPq39=yZE)OCyG80Rn@vtVm5a>(KhrKn!IJmw2}IQeHHIl7kHq>19jB{Q4P6I{;eGN*l!_sh(iLi~J97=KFNEsu8oP8kQkcWpTMMdMddt&4h7YA??pKccUgq z-nSy({4Uy6o1zU;+5Z1~C_C+L-i0<=?0a#I@dlPG9?^b3jqmsHIfKvGuZizMeEaaB-?!sK z<>`4VK9Aw^dwlfY^!+A2C-9+sBW~T1eLg<-;Ij*#L-13n_#7Oe0ZjEw`<=KeCyBjP4y2}$1jmi?H|-og%@75 zaN)(XW?gb=(d^lCF1x(AA(N?MS9d6!R5}ZA1Rj^8HLrSY?N9>2GZqmNF3Uj&;#A(1 z{yZym8|W71woM?zT*FLS*m8aU3g)LF4pLwJuhUj)N0_kfxK15%pvzO+>gp$f2KU zCp*C|<3H0+cG4QFr&^#gy|^Fr@vZywx%l$IimwsH6Qwk2J#@&=+QJ5KFznt~|85woPgsXU`WQWtlxOJT zDEJ>`-4L-pOHmAJ%CbE`Xmf~;H{?7<{&1MSaPU#ojz8l5BA-NZ-Rd{vjpNjo&PUXP7m}BjkOeW*rlUIC z*>v2Iv4@lhk8MSD0VrJq8D`v=zzlwjOrVS-(An>ZWk7ph#BFo*_HIbDH=5c@R8BW6 zhN_&#m+@%m&qD+1KZT8^)}IS;Bx?On^1*%PKwx?dwn_&A``)((Mj+}6pCphT(J|tH zoU?wArU$fFFrYoTe|sy3(O&Z)?EzrRf38AeYkPQfS4JzIFc3mFkHH$c8$9xI4i!RP zrj&v6L7Q@=27+v0ZCcX2EF&LH8a2{m5;ejXr69{x^DRaFCyiD*8nu&GD6M{a9ISq# z@5ji0lTOwB=wEj{l)=<)=#vz0QzT(s!qrh*3oijCaQtX?3$G3_oc9}uu3tOcKDmwR z_g)8M(Du!#+7IdNJVpKFq)V%x z9tW#`1y%~(UvD!Om0Rtyp9sWfz-EwM6gNS6>m$8 z5E43jo0-4c0^Om6k5I%PYn=v?x>2`23Vj`$xO01JV1$W7>sSfl$)cQ)QQ%5= zmzi)dZx>?ubU!U`W+ z8xZp?-IB@PYDE?!ay}>?4su?nyS2{(`@m^>=my5@xeaO?+XC^%MZ?6KRT?IGNUn)& z`T_pxZo!|YSEkU|u1)-4NPitRy_;xo1@1|^>QOhdB9PdV8Qx2|;i9t!-#t%26g@|8 z8nF_NVe9w_>_qg@I}pO?{SdUG!8}cenOsi62UkCTnZ#0!1Ily>y0Be>+<-B9gUMSJ*6b) zg`{XCd^V_W;pm*)Qg>5e)k$efjzqt20A`X7OmwE{m(V786{lw*Cw*1E<*AXkQSi z&VvtC&z{X29?Hhf?FabsL?WC2f!K`Ic^>==n(ijwWEb+txI_~6Zx16QOypu?J>%fI zwl9-RoU=mknufgtL-Zl`df7YPhsEUN{2KCn;(j#_g z*dCgKPoh|`9e?!dmCFD-ds1i2Xf5bH72o~OgN*cfxIcZi96yGx12gh%sy2HuffTEf zRt9BvbI4W(#I=W9J>&6Ht-v2T9LdIaQM<-tdUEhfsc_0wyNPAD%i+UHMK1j4$>E)- zi(M-2#}WFThtU#zD&k-TUr*2mRoP5O+j8yWN;7cTCsDJD za&8wjyXko|o|W3iOKZFBZR^E*%A-^Pe@gRyJmQ)zFx!K3XuN$QR{I1%4Jk_wftkl@ zkI~pj$R#%&g%t()6ogrL-X8=jJcMn3H*TcFHo&oBn+*>*@p^POah_1S^jscZK&g7N zyR_&tjC}eR9t}6-sTy7jpzk_u-mw)q#?#J*#KH3c{n_bRIJ?G&DiU2;^S}AJqP$cN#oUcLg^$W2)n2b8PI2F%QF!V<-`mGj^T#&kqHh>h)|rfa*Evb?bmXM3L^pki;q z6{t7%mLCrt8b=f9#y4?xs{8wEIlc{r{W!jX>!NOmwjSY+*4ucSPql6$62jRIwBNaM z?GvS)A?tLyp`)yF=w7xFZ_uw^n@t&QMz8f<8E-&AMaltxW4Tx`)ZJ5=Bp*AB6!eXV zsgSPgZR7>3&m7JTb|WcBC4-d7ueROD!$TZ9{D$$)2u#9|fdYKM10~y)V+E_V?V<{B zsVQB-^l(r-C+oiV)0H`tna>+Ej-LozrBRfTzaQ<--*NCw%O0FymyxCeTpo*_q~69R z59ArYn{WBWu7XNr&BWldpuk`|Dp4QEp&mmbwYSnWH>vE&RsbEc@1k!~!)K^tuA%oU zu{@c=jml)pe?XJcQd)asXkzf?cs+{~^?+VY%1v|4oU^6WQw0HeV(^uC*7FisX<67s z6+X5K)hm@Aa|ZvS#^i4u(^jg;V$91UO8rrC7#}*1oKM#uMG#xK@a?M3M-?V`K;|#u zdOv!pvgDWo%i$xix%9SlK*Swck`uVhGO> ziY{iudcb@mydL}?)*&QskR`Ve$U>JSNn$f0i7jv0k=+o9e2y6|9Mi+@=_fuah($jU zc9~;iHX)vS)fYKm|Y6jj{bYDP|v zI)`dxUnUncUZG@q)NRZNc}BdCVJkeuR`I;{o}91s_LgBQC!z!H!V9g9?ceFU#P>Mnk5YqV-_kMi5Axg{Fg ziH%uW%5hGarKf~5MP~@L2pLS#@2D8Vs>&-ymd$RBI&PY4%y^UPlz$q zFkG_av|aigz(OTtssV%JSCTT|eK|pSKjksDz01@BXlfD8qbBC{Go1gCRB{neQ5Uoz zmq)!TMT(PCZb^~i=9HQgDYH1`!W1cUIb}?Wlm(n}>N3Wm)@=dXb)52Eij?J?@?wgV zm7MbPq!i%j`5)W^>q6iNw_$N_`m>+d+s0$M+4q)bNa}p4Zv+oT9}NYg@q&&eXvr?oS;FNJGQo1?i^qhg6Kh7y1q)0iFjZ;RaNO_o3`eqNr!ER1@H$}>moU%VfN;{|g zSBjKFoU$!NN;jvhNs)4lQ*KU?a)MLlrARr=DfuZw+bL2C zIi)>CN-?MWBt^=6PT86w#ltCgq)54$Q?5^u66BOCQ>27A#hoIhnNzY;q-^Ds6PFrs zVBnTxg#qU^5jNn=#}~M!MdO;kPbwv5YAe(4&J-!zIAvpsl!rNGWr~#DoZ?H7@+7Ae zr$}k%lqo4v4snVlMM^iP{NE);EYLCyOIUEPqgzl(mqR3p?}+@rKaG4zD~i6F z*|3@^^{0JU%={j;JgNFci?vo~B)x`Q`|Y+J+26zGAU=P_=SzIXZ`_f62|kPPxecEU z_|)V5V4odG|1CaeHtopH-Ml0FQhfeDZSMmgb#dqaZ^#A~4D5nIqXvx{H44^Pppu5z zAS7V+&Fu0Mrw>ssEY>pk3K3Xu3yfrQkYtk)iaqQoR`cXE5EUWeVTigfPRJ2^6N zrry2#C+_0g+u{8VU1viuyN|Olv#LT&w6REWO(;86I>F!f)jgg#H9J*WJO{KmarD-j zo4zyZ`uV|TwwoNfetw|Yg=beB^6av7YR;kS=kyf_Z0%922CQuiCC;dAJd`-@P(fO? z1lLXD;UDRCkaF@kohyZ#%^7|k;F}0&;iI0_8fv8EdyNzlu#x%UuaP?5Xod?!K{dUsSW>#&pC4&^5 zA!9osf>fj@VdY!O?3EA*u-eR?uUmCCGmed^5xv8m8bl{m+^(=T^WWdeW^(}iE<}E9 z%%j@Ok@<|=YBq!=6lE8s&HR*vj?(<{IUMO(QS;kJa~KY|`&9EYwphQw6PU}D2=!tL#>XwG5hOXqYrGY8%JBpa&!7UblW)Ih1p5SVdvHQNk z_EvqF;@{AZ>2Ce3#nzy!zewHGl|)L6RF#=sT(#L|$rkXd@F!L7{1NeEk#90S5l2e% zi#97^KTr$L0&~$WchPLDp}n2+O!aF?@ARp0cKdT+3JvFp9E`hm7MJt%k=28Y6anwb z3bx%H{UAykeRFAS@2Ce=pL}6pV+MzrW>iPL^J|)63+;=t;{`UkqvB$4Xa8sR#{hbi zXE)D5|B;{EdKZT0{2f2C{ek)vz}PKB-K;M&d9SO_g0UhOAO0uy2lwLuR~<``1wF`b zpQ!of;GX9QTHyoIzxS?9?_Z~A`2qbiYFb=+3g;5~ziz*b7O)su5MU~Fer%qivNTP;n1bNtL1W34(O6tUqbOuuQ6ZenlA64`K^mF%HNsPK$T;V zE0?Jyn17-N_&%AE+gjW{NWY|o_O36=G`t96TqBhK%jpBb=#!8W_BZ~+GJ!J-(YEeFad-B6rDudPDt#cDnE@9B+t;(>t5~maShsnvCwGR7 zmXKIAsl>m(EJ7;zflW1>sN1C%c3yXg4j0+ zlF`bTOBl72ra`$OC{0qP@W~22B z@sZ{SwU#L6YMXJp`G+x{yV?)34hC9eSp5gWq*!> zu6H28atstSnQS&I90Dq_@zi>L3YUJ9y!#6@FwhkbZW2s&?xr>iBIm0KwoL@=U4)Wb zEHN-3wuZanG@FlUxa*5JY!fB72n;J7t@yZm;=F?$A@dh|^=`24V7Lo)qyScVYXz+z zGY+Iwd7biudYQI#T_(fZKVWs&5q(wbGQv0Jmg`Dcg7}uJE3JKnR~5 z_?SC|+zas=(>F_asXaYWR{NtH`AU@5{^(BL*aoH7wV8VubM{}N4ybajSDCUS-P^K` zu~B%3#Exe3G#fY3dJnD;)xIs+zdkp>rbVdGw5z3E(GP87uVZmNx%-K5m#li_TjxF; z16yZ&13PFmDw3dNGKx_hn*u;YtX3M^6o=qSn}nTqN=x9`T&Y{)e>2nPMu`17;2 zQwPo2;7g^BPi|4gWD|eNtks90_5P;=&%KI+x}yM@M*-2FYyTJJv*sysg^OR6V-J@W z7Ao=ChN3W3M|0)TLXLT#pAW*!q2VgMe+)<-n8j2Q6PB-QWQ&?r z41yFo0qh3ISf{zlbw@0RLQ)GR>FigktyD+6xFVLl(X&zu%5Iof)yVa*H(XHFw4S@D zXH`vKzd*K|CarIqz;&^y1@_1r9@Sh4pIT5&jCo|?+w;iP&MZ178{m^W^dU-ce{G05 z-s_4UHQSzn44b&xd6-<|o|Q{`I&pl#I%Jmw#}?~gUMR<9d!R~L*kF4^6Gn1+ao?cr zOj`fBq3QUj`By%?rW*F|*q{m1vr1QNGs6nJ&Fqw4DNd#DddG%U`>sm~bEQxbo~X41 z7Qf22Xzh9#f{T}IRdf1ntfRKr>q14!=4w(-g7ro6s)3MpdSo%7LNSL>)oYX+*WiOx#&N9T6%%I>S{u z{MlizEV-rv_R&cpy(LrNS0#4igm=ZX?@!{82=NQQQ~g z!Y}liF7(&IlDk_ui%Nu0`^@#G5UH zUG&ey`1I^a;0YINm+^<|UbdpB$NZMy!%j{kpP07l+xM${TY|xuSTp zC)GA#Bv+NVhYvZ*|^RD`mH+x!bQ}o)4 zYMU;&>OyaJMQu~VwN156v#y!}5eoN7$u-jhiAbhtil`L*6}*Eh!@Q>HU_}K43rlQC zE{9P+ZXfl_GC(VTI}@zst=_^Y%`zr^@hn+VjbB*1A{uYWxkh*mQL1#9w>@gE=~YFl z$)=z$tSz~ZA#ac%lB^F}!SqOPv_8DCm}@(YeIl4YeD zg|JZ1t88UEm??k53NBRMH(V4a$w_d@ndk%)d$v-g&karX-FZN3*N*FYzwz#` zl7RqF2-OY`ci*Go_^9MxP7QZ$(&qwt6}GR~)5KA8cHb_8x&P%@n(TG%)F9Ew+NMyr z`x-@^846)X9 z%xlNlc@fO5+uHn|St+nkGo-?uT;vor=kNE4CEWEM^_Q7hB{0ctjFHFRr%ik_E3uc& zCJ+iQKx$)ertJ=M;V5)vb_CFso79!r(%z=7eE$o%u3QRPY*rHDd2?$rGhNqdHZ%G` zCKcW^sdnb{Yrk4Mv*D_1yrwC&GiP0UMeWQvS6%AOl)NyiwkZ-Xp;sqhkTtb4FTXZg zJ9EWVpfIg=X8hWjwKG>TrN;)PfC7usru|p=8-DzF;CHF zXD;<-E(Iz8g9W=0&}w$>P>be-5I3K~5)OW(ozM0YXgFQ=6P(ED-p^5qBfCd7s}to- zCvKAK;w70N+IwIX=q!>4=21)7yk#q8tKCEMh_Eu-M5nNM0?XLW^Ne+scYKViy?LW^;lnNd7AC?+%!>5Dm7Y2Lh#MTm})x2!ykD{z`vGvyH$(&E3 z>d{;9*{abvckDlB59_b_6SZJej`|PLO_mBQU6@}m>4if#1QMSSl@VA)m@Ozg`p|H7 za@oKnf;IluxiWZwNd(sJtzfiS;&;I;)Cvc^VZ_5ofv=!We$A}@J)P-=aQBZP)L0+= z-YlxH^H6VZ-Am!FZ{WQJ-$14~GK;--mjxk4VNiWwlzh35M#LZs(br!K2V zT~?WXNYe^>kfNDw{I;gOTl6h?J{_;B+KO@^@W%Jko>Ua(eH_UH)5*3*_%ExP$J~5g zRW$u$X&Q{WY)4VrV;@k!Jj0LolzExehs^K>Vfo`5iGlLzIVvgQZsk=45Ge5Qg z(+S{0GdzG0NZl=+LngV4-(}XVX>La-c3T%1+`Y#JN@!K&Oj5$CzS5rID|Ppa_E!>X z>TZdt<_gN6DXt`s)C4uHrtf-_#vcg~Nkr9V;ZuNVDsr|C=J{-N@-wypQ)5#Yk!lX5 zFdx+%Ni~^Z z%~Ee?YR$4t(}innZi`i=@EYAOn<)~>A+b+>A-y1IS^+}0i-2c(0W+pDD=?>)RA!nW zeezvWWzJScOQxl+n4VrRFSVp0b;YdIk~!vB>%BO&WNGS(WvL~x)D@SfmaIr!5l?Mc zOG=>XR*~}5hAzQbr-?teNmX=&7Nu^|%r?`s(xJD?w)jn!aNAW;l5df0>F-lILebPs z8~YN@pLywTKdiu;w&?ea)Oo?wh8=vmw%)Kyz8ln4QtS5c zyA=+I2eW2=QJ?WEz3KW5`}BL$@9g*Q`Aw~R#r_)mdqBS%4%**0?XR>SsSQ$jq&A3b zQybLHQtNcKF110!U221rbg2zx{I1_nuHT!EvEQ&*Vycc)>niQ9dNGtcCAFbi?hTXd zulPx3xcaMEt-g;%U=!y9Q32v z<)bJ8Z=s=gmCw4WebzN8s1j;Qj%jKbSB+TM*P?47bp*CnMOX-_gsYe%7?uZ$f4jFRpui8}w+bw# zG*6jdYW`QScwp(F%Iec=n4`bTr7arhpEBpXAfYkb^+`A?ovgxpiuS6|u!;G4P6efB zV?fmea5J{1uNTMmo{E|=3KISOd^;c4i}#DuDy?cQYEGgCPg7fj&#!1s&#f@ieb*#hlg!6N zNsZIfEz_A3Ynwu96#YKkI4j*UtI*9-(Y$DC?!5HesQJs|pf!JKx^Y>$Wm#(O(!zL` zr&}&BbjQ;z@qV{Ae^qMks!Z`5%E@&HFHrAQ9cATUFz zi#YWZp_qaFe{exkyH>PiBdYhP`7WANJTT1>G=0R+)g^wFW-cD->(^7ww8nXKLfaY| zg>f&+xiy5Ubym>&>FYJ_5_)ScR70ym7uiq^uX4?@E)A}7eZsml$l~hEXyi2~$#3={ z1s}~PW(;5dy+y!{r&SDFvReF9i+PgZ&N>lKxcdPL>WHUhF!5}J-KK9A&9t!`5J3^l zCx=YF;zzt$<-+}MIk(Ey46%OECRDiyRW_=Hn^i7_o^L}{F1hNgOXZU5OzTp)aQWpr zMt%zqDR>N@l&kjlgUV$mTK-S+K&6^m!?MiIvnk}0!>J}TF2Z8n6sSAe+_^JG9;T8I zwHqPku1D;5q)iPf_G5-W>U_-kd^lo}mM*y(D_ehz36S}(u?2lY(=uqkF&ItH4Z7}O zKG{Rk#gk`PNR#k zr@8^L=gJ`WljE3+Bi%Z0kvtmbr032{&75O?`xt5FFHScuP0d}Lrhi0;oNkO2ri-N; zR}{K^N8?J6eP{>mpPISSYh9IUS>>hI%IWFX>+aHbQokIBlFzp!5JXeo)XPgtAV_)o z^(EK^!X>>C4*=UMN>iR(-nZ`LH=6eD;+6WQ9G35RX$}7AZYv0Q_S@69ErUfk{gWNI zb3@8cG;F6I(y#B8Db^@!dpEP7jxyuXi0aw9v@Xy<_vJRaxy0W;#dYN=>bdA6%A?sM z_1SDDxb9r0?tD5c3ABE>9el(!SAh=1G{yCxO3TG@OBo(D?>$89Cd(=CD@|dFeCLwF znVcg?ONX+hUE0=ee(84xM~3a@QQv)&$Pq1LHe#W!%l>W8Kn^7rZsr!!6je`KSL zV|(7q%J>oWw$uiSAIPlR#;?uSO5iCgd~b0A_ilnx_tI)ahWGNj*Z#i9uP=PB`f>xzai&nM>_Enp!hmlejgS#HH5EGmG$M%E*L~Nt`B*b{e-PesOBa z%1o1;oMnbf@UeAZr;&MN6@sz_TDP~bP9KfQstV7uQZAfTcB^FDn2 z{`nFyZU2NHyesa}X1+*fFpJ9}r|xa(*}-UA9DUhuZM^jCG6^nAAh`7Ga(%Yg&q^et zpVQ0;Rn#E>J2M&*@SWmVC*r`TgEqvPDKZlVzV5ie9;wgepbDuAat@2susl-Q%VJXqsk3sCgw#nnhmabNqY$ZYxnv;axi5>98-M!8 z2lI~>Go;d}`MkpyShcw<%W>T3S|dGMO?O?+QLPl~EQzRCBXTJkaC{~gX%3G0Ifp7( z8)R%*E>!Z@>8$D5i}m@c{anhFESXtm_Ttj8w#WA7K||<(@y)EV2`&Ct+D}cxzQe4` zW+VI$`9r+L|G#q%i~k*YTKQrwh4BA*E|T#7S+4YT?F&tX1Wgfc0m73)tntu2BQxXHjz(cx1BPko0uQ;4|_ThYb~;e{0v#qU?t zg_Q{So*#8#O)@iu#YYX*Aw~Hk4NCj#@U%eLMaE!H|6MZ33FWQA;GBL8Cgm}>0I=lB zJj;^sl>mbkMVz+IJ2MD(tJ@oAu?QPOv&+-7nG&-a4U2JAaL>6I;2sUC2f$(`U2&EZ1j~{jAXEYc}BoKGRDo z&CAwxyaJx(v!y+KMU{Ebaq4tajr>QEwzb`SKj&FZp10*3lN7_y=B&df&QGqv;M>gP zn$ltM`s5nUu%44#g9YHT*RGimNK8!D4U3U+cRe1wsND)nVI&~LC zH4zQYPA6NU&js_$Uw^81wRy|FgXT5fWc>&e+C03Pq43V<_D!2&l6lC!E6k7WyVTt6 zAgn5c@Mo-u=P_z)&lFYCIlXt&DMRNnUslSS@8PpEdm%c$j8#KP{gmKp_4EVMBA9VM zvWT7aCR)UsZ^0)sT>8GwY@8G>-U`M0)4gaTOpMwg!}QH@8&mVO&g?CIOtX_X>6>4n z`ORN{qR-v>{2%+dUY~!opUd?5qWzTO(>!fIr4lt8qzkzDK2_>N)>i~;?$hVO3(Q^i zU1K)dcd5C}zC$J^>HC-FbG)eBQW}fiS>&q5Szla!r}{A5OVp-*Ruy_IBqYeqXUy>o{U z=|P@(!7icNwakHGtTfK7U|VaM!+}jD?1HQXhSyc4ds;X(z@$FP2EPa~Y$(gyc3q>b zm2{;!-x7QuUAdm+P1TI|md+2q&R6=Y6^C2!Omkshe#tXge;;)fNHpY(h{)P{WgxCw z=PMceCb7tQ8t`GNta=~o#|n3S9r!k8xO*L+y{j1ACUq>1n*Vdij~ox~gc9+tO5a!| z{Su2W)tpJk5BdNGEr>6oI1htI(q3*mjl8Sy*=Bu$sU#a=RO~7Y9Zei< zKPQ0?FjeYTe9$Wcm=$vht{!z{x9*9W1j(R?7~@0;2y~ct|~fDDFW9sIa^zAh#vm} zn&0d7Me#(sn3DWn=z12I-;RGOnJrr#iQPs2%{~?j+v?vkogcOv*GNNmSxtpA0ci`h{zhS3e z`z%c0nRbOLDDT>bOaBdUi{r-*4qLuy7dXh6!Qm?DY5$S}Kb5u4E~a zh8|pR={@05xv_PV^WhZW$07aBY#;tty^8bX*jss$ICID0J7k+_fuI?*W5Luvk8)M) zI^AZmu^}BeRyH`Ac-zouHG`w=IBc}(gQM*_Y_xfUqwPLyw8ev??Ky0;*x+c-9yVHh zaJ0RLjkb1hG_mrb6>{U?X!{NuZR6l*uN*eoeS@Q!!$#XSINE{3M%yzu+QGv{dvQ>- zi#ESWyo6ra4jvld%j#gdk0OfTNvIn5g6k~|RLx=qSpAA<1qQ^y+nPJ9aspicwU&a$ERj4dk4H z`TXP;mM0&zBVr=zU9TZA+JeoajrOkA2#T^ZQxnv6z1g9EB}pW42FA>@;d=eVk8d{* z+-(I~T5~K$-AAXsClJe1-xEmhQyjM3XoC0HgQcO4tQ(2V$(#$zlnAPj^%1Mq5XVeA zfBnM%`?s-Kl%UX_bLW9d#3o-vp`;j8iyxb}i8j(?;cg&xG&Y|9OeQ(eM>e1T6ZfMB zX(=Z{B}y5Y<NG@Cmt_hKM-LQtmhMYW4H znw)NY1v5;0I5uc)Pa+O^4Kj?&E5t2cpg<@< zQ2a>W*eCYZvh!c0gx~HRgDp>T#I_)t5?B%*YEIZu990&wLJ7ZiU+4W*llWw{ew}Ui zHgf}cFsex40&>3qI28`g7;NRP{na=arK=%dt|ANEK*L1%f$xfeF(_!Yzt~06*ioHJ zDd|?s%R&!#Jp)&roPE3Lh_xvO>u@*6+^oFY9xDSJbs^S-C=MptFJ0Cis~Y4V?)o1J z!rmelLR(szqiyU$d8bs}q|93<;{?UXwJj8=CRRgIi4p_AmJ4i4tYHw?_yhsej#NOJ zfAIlsumIT*tVE1a(Lt%~WI_Q;2L%iS*Y1P6*Mhs863HTH z$CA$^M4*~zYm2tsPhx`dZ!1+ei#8cuJ~*QiySO7`x>QSLycN*M!wc>zO6bOB;kI&c zlCuRlSJa1&pk?RjZZN<8+Nr=AaG0WPudA<7<`K=Opqk`YgGdSiaHe@DiJ-_rcl%X( z#r3$j>GgSeE(qy<<7}8qS@=yHAv?co!`;%f5DiVqXd^jIH1jWte_0R}36Sk%%R*Y4 zI6Ao+P01*cO1NvSONmKbj{1kXn?i}t=*V?`{*8dKx5w>Ok*b%wnO_!m88Fa zMm8&TvVP?N7~4J+#*sr{D7Byc-a$znDS-k#n#Sj*Iy&{!R6ND8l+YH0xEmJR3br%% zak1@iFE>vpf6xkrz>XxRR|n#nk5I_Y1po+C#$FkeS-hsOte2^u6qZ%Q*fA`&AMn{d z^d%0O#oh!22R)+RnVQFCpQ9&qe;eR_aRS~TUMQV~syQ+!Aiiu+8DASQsEmt0+h4|` zV!in?PTA@N-`CKr2PB4Ct`P28?Q)nx4mI?y%4iF~C0N>V%AK06U0gLK+~un= zrbNwKyWY3g^uZ2{b?s za-3+nS0pW0_|ZMfS=XEQ^luta~yR!IA$F4o++KX$i4PK?2yNIb%tQV$BpC7%muaoffDb6Pnd&Gqta;z%?6lmMywELg_2n=c^LR#l^cwSW$oyT{2}y$G#k!I%`k z$nU^}PA97=t}-=8w4!PJEYO{XxfMDVevy8|~LEIuHdB?41*qt(eIHAR#_fi z_bmsYMrpjF+F^+^insASiP2^h7lNUbex~RBEXl>^ySJr^;up)|jZIIw1)KQAGeI`e z5H&Xv%isvKUGTC)U#ZHGQyMgfA|L)TQcZsq#)7}PP>FaEk}yXlEs~On(Kg8)5sNu~ zh43dSPCxw0JbW^X6aqi(gMil+0HkloGynYU3SB=beq!g`EnMTcn%{D6x1w-H$Z&ti5kW(NvHHdkVA=wY0X4P+` z&9B}Q=i0iUl*=+u4Ir#yKERn>Mg&_Sp^hMfMx>cauIMNwR{0l+wa6TGoRU&O2?;qi z>81!3LWLfrLOY5~7fqo`qf$UALXG6gwnWg$^e9dM%N$F5bX|!)wg>^Pl2KUzd_wHU ze7L$g=iXdhVLZnN=RR!lH{0Y&XDi;1;0m5RP`+somY663r(`XX%)Z2ov|?97jnev& zj%4cy95SE3Kk z#6Jnt@{Vw{HTyhe39g5S$|u$b2=bjozFg0m7}=T~KOF$Qqgpy5d^hkN+eOyd@rS_1 zTtQ|IIFY}H4HlcxrbD+{)$G_<;AW`{XXR1Y>_XVs$`nRJ?i>Lh?v^6VKfL7b8BOje z&vUutfl}lxpCZEN`xJqJcd8TGs}S|-gf-+qJ++|)DsQ3lRa$DO5a)@SknMI*L%53_ zJly1B+L(dU@^u+^oWI4ivf(uPXiP(11e{~~GGiH);Ex3hI{g=`-j#M2tH$(U2L ztpTKJF!|9S{;%+^^WjbYJHcD)!}~+oM=G&s1(Kn(QUrx`?7MWjKm?mRu$_O$2SN$? zbW(8ET?F&t^zv08TY72yK)8pYKLoIl@MPNzjZq4|fkm3&(<+E&Lb>c?w$#n$wIe^M z)Da)w$iM@e!uKPexM*Yz@Q27z@J=@3krE&ic z^}!fLJ##DuLj!gXPDPtoNHff=+>S$KUjuy9taZRqz*<=XE`0J%7?QYmo*{v6sQ|Ij zNp0n=scz1=+i%)TWThB%DMzW4ONF z|1QKoh&*4GJRmv?dJc~e+W;R+?t33N%Pd|PA_hA!DW|MUw<7=Q(7$B&c~vo#=|E`i zn77ExcQ_PQ8IdH8MjtCvtsSmEt^}IjA4liQFqm(DKz}?Oyf62|`x%%*T?{TC5uWSc zvI3zkZ!(q?5yMg?o5Z;wJcqM-ycvt1%{e+E*ho^(T3BPM|@V@Z(fp>Kd-uS-{ zyhIM(<7`FyJCygD9K6Ke0`DV?H_`%VW<22o;eNn)cDVjtFJNvwf6}eA{;mDJkiHAY zFIvTy|2`1zVaBh4^j!!Ks&?>B`CH(*`i#(@#fzoUsgQgS>8ONFm5Ogi#E?~=PactL zciX=!3;K7N9cUwt4d=Uf^1u}FiZ%^4?d0IV!G!}IRhDCXH4pJ5jVe`%v;`Z zL&QSOhOSr)ehR#93z{^hD$Zf^5PW!P8%Ty2PTh#7)Xg1Li@XsX8i^~xfTQYT^En;w zaynW$eWYCS#bJCYI4?rZA#X%;YIdljGBW}ZB+#C^HrP?g{(e@5UH`CSWhG>y#n zJ!qMC?yDcP{Tj*-3)>f|&zJx5@CWn5zlmO$NI7_Kjm+Wu{|O#?C;v>PoEDwMBK=}28;k*7M9^nMxOG(+ma)X)qiw~24XH|V_u=xAT@LUs% z^&$Rhn|SbI@gj}!{>O*+-^zSi%9-e@T*yPx2~RNsPT5YmuP3?J8gfN2ji z5Edy=5bj0I$mlz~mL`Y~W8KxAqsvQ`#AL4P&%a>%%)emrT8%GlV<_-`Dq{b`+>q&tCMGzO=N}eCc0ZXTUm9g5^fHE*F9v^7j~JinaQwc~o9i znEq<1HvHxGxl4TzVdlk-Eb}Ea)2`Nah@vbQm$q6%FFLnN$cw_2F@E&tlhYU2`GnP( zp#i6uW!l{y=^~+&5aoHWcF^X4gVXvkjc~20w?7Vvmyk^d|V<*8@r@u??g1 z3BGNh26i$f1?Y>r{KzfS(mq2nI=uAn@Q=n;NV~a|!xH9)kE;qwx5y!-FSrERM5OSw z3Qzmta`cC9RCt5J|IrVZL*dE#7a=Zg8;O^r5xrs<2Y;v;Dv?&dnEkWw?CqgdIgX9q zRoYHAA5!7ymr+4#Z|QfTx6OM+pJsX zZSLpAbS(s1NNsUSjkeUNmh4gk5T~I;M%Djunu$f;PYHjws?_7zif5~S<`tyUt+469 z8+W&X{23O6+jBf%snJp(os1PMR*+`cEGW?Us5ezh8rkPFCBhUy0ag~QU+&h5V>b!S zsG`_wEDEA*a&;dcZA0Y{fK9I1;$tbpM#`2cv1FnU*BJ2$3~;4{YJJH1+4Ksu=^aV< zQ00)XuMp2KR!}0VyGg!Z&h^Wa;n8k==;&K>>qD%-E$Q)TROX$R^jOTit{T-ECov1s z*XPs!`kNn|-W#ows0*jM&K3~g!#M_=?No)szb9{_mj|__)w0O4_SS5P0Y)vGBNAac z`H=+G*s@`;nw<-5DOs;*wKm%Btq1{&bR`WnG5ct_1x`0=&E=ZkeQ)^j^Io@_{ z?TF}J3N|v``8!gYbtF(NJ&VJk*7a$^>CE?VtdqQ>*$CDe?I5-jVFFFR|MafIqJQetaW&vv;aFS-*?o zW0Ewso~2;7fVWv*en7bE6%g$RY?2>)V1N_M!5LpeiB*>EM4@Ubc?DW?4O&gd7SpQd z%xnN18tdGZy>p2stAl~w85k_``33x%)ol>21}V1qmgYdHgVGcce^l(5{Pxdo`y^>g zex|yxkvVM$LG`P~z*SW{L ztqSa`6BH;+>3YmT>PpPs`~V-ooE|Hwr1G?c9NVH1cn3c~i2@~QG{PRId>CSB1uhim zN2nlS*VjpS3qOZVxSNzL@af!2vR>RDkihdRpVuaJ%mOz>4Q^KXeUYZkF5MypPOSnf zySjb6U0G#Z)>8LayE5xLECaT+mgkeyr?xWegUB%OWDiZFFy7b%@dr9rbKP*b*LJPP=em9J*Hu4{1HbSXfZm2On@npt+W!*?P~z%422mY4?u!3CM`za2jh z415vt9AHL(>Dde{WNmYgkN5ri^TNnezkr3pSEa*QpQuxQ^9}|TtN%*uF?>OpLCnr?D`_T zgl*0igzMJTJ(&{0rQK0r=B6gqbbZtR~TIVrBJ`E z7Gcw$tN_#2k)^a6{-EXrC&i=~biyAAr-EFNCAS4+M1S=TLI!pyc#U1ccrd7si#>=g z$H|kKl$BBn8HjCO3p3xe_9Jkpkg_s_ejH_(M9n6Nm^;ak3(Ef2SbolB<%f@H355%B-aO9U z<0SIaO6ySa%SD_!AsSqWf(^0S>YgGF=Pvj63CPm%&KCmmy4Z%{P-gceb|L@M<0U|;UcVMv!3H?LRocQ?< zK{IL&d~Qf^`sz|P21Wc$L8{DM$$dr1-@VTSvpKhO3KhpGYZ1s0oH(n}#h2H@8BCbj zZ(Fk2hQVy*mWtxrN1ewL+&jy1J30>gtXt9W9ss#)Knd^&WR*SzX=uqmI`1NBJv1=GfyZ zCLDi4UFC@yvt+Neltkoh zdv64*91@^cAPPH=VX*i{8E2XUF*>gz9oJ^@fz#P#$!Vv?-JP_$HrcmN!2RB**~wmd zCAJFKPR@DU*X$|Diy0a)p(H2_9POP;jSOk+ra23)yB%uCBBiO zY*D!TJA`yZ%=0(7F{-gonzt@IefVCVx!rCB;*x^CRO6dewAcD3hoTzG7PVtaJvAd_ z-bIH+^LCJVV?j-k#2iDW}AuhI5+@ z_o(Ayjy-~-H>*&nnB<_?{9z13-L<}(0HzXhZj0CWg(CEn6a($)n&`7HODx^Ol%jKG@BYLwdq(z@lIQiShPb0z6JOysj{(G z$(=axD!FrywaXi^j!SJ;(dORK)Iy9CPs%yk)@p8IAt1?NdmSGLcdZj;ai1YKGB#i~ z;k4^U#<>~voT#&WlM2AA7$? z)l;`OQL?jm)XatYA#7pEE~UW23c0qJ11N_PR9sBf*E&QSg)IREXy>ap1B0=PTFqQ$ zC&c%LC(W>AUsd?}{nYa0CvI53sa-i2I)5lYYaPO`7!+EXS!bHgoV3>(?1Z|$SHRQCl${DfU( zDzoF${up3wxTAUcMYOrq{7Ye!Mxsb9+!iZ>J-3*Y+dJygSLXwV*}%&S1CPiDvbapj zg@u8CM$2ufb|uAt;U|i{Yi7B8N^I;C3uA9n>|s=fj+?oxe<(HixyOdPAm_j%7gGO- zg|V&d;H4_a&Po10L3)8EKe;| z)vVwUoLj=^8y@$w{^I96!QcbNNF&b8{-7dRgndvI;81#;ESm5Sb{gKd;zgm+#jwJ>VUEf}s9UrG|v{bo*)j<^_#=FH-0~hhY)lY`ijP&_|ZykwD%d>0H`=vqd zin7~BeTgSfZRXj6zmhjxjbN5lV zkNO_(FYsQ;^90Xt1-rDUls^g3dY6_C9}yZk%6=Vj1TfzvUVwf#g6DO82YeKYiu{k_ z;$g#_3~bl)yWd`Y>LAKFN+TnDkpt3g5YatUl+JXZ7tU{OvH*}jWRQm?@`&zYMOwJe z(i^O&3$p3B3?B&=_Cst}Ru`hI1yr``SY&&P`8T4$a9jCY2!+7p+dGz%s*p#|tcIA6 z5EN7zDIG1e(e7n?9QqRJkHv4m+5gg~Fqs}U=?r_!4 zs=8mSKH2+ALFdnJwetFJ=IjmbMAhy6H6LyHrrXHVI<48wzxs-y;{6kUlYH-pHMKVT zIdht0-?d1W^&?#nJg+%+rwx81AfEkJ9b1mrRu;$`V(xc3&DI-Yi`>+`(XB7sIn0wa<+VPFcu zm62=C1{`{QUjHNzHFU~%D3(HO- z!tC>OfGvE001N0Xu_Aa8OHaa+gR2U?pHNGVB0M`U%PksGSAHF}KBNjuJ|as~0ZO-F z5+6|885G%H#X6zIormTibWeO-OZL~Q(z;Ci2+Qwa-ZB+vz zcomYtmnoOu@vK;btT51rorN@xZga;NyyU(shJ=?BuA70H8q{4s+v&HJjlE(>>>y1o zI9Cn{@$=!nZiRnoP`Je*bcqdHIWWwTOa%qJBz{!ER6Fp3KhW+QSv3mdS~>lyw6pQqY0+E5JBIb=woA@+3-++_~<;2o{LO z4o+a=${seL<|5_u!$aqpV5wq~)n>O^isn2>(ozn@!|0}{e{4hO#YbaeX>1R~9`z>Z zRJ>MH<4dgXUin)00_)zFcc0sqI;Wqc0lCXjcX`JF96NB_6-pdo=@`z;QMw#$AI_CW z-G11ipwz414Ujf%3W|e!?^}6G`nm8ugm(n{W4uAeG6gVr2Ojmo{#kp!ltv@fyY`aO z1L?puC)}>Ne}6@W3hsx%8WgMYGB7MkroxUy+cJNRs4?h;Q*(l;nw&L?f_uSiXJx<3 z+wl{;2}9}BFYjufBqw+Aan#&3DM$c-|2ITnB-q$s&=qPGVjhB}hbAiwg!3E3B zRjhmHThND-CEvZ@F!Pl6wnWO$7Mp?uXxbF+SQFcYRB6t*+``D2lNi-Gy04iDeI@z$ zwo3~K`OX6&U_(E?mA;(l>cgLqhd)ViqUQ1jTQnD}$l7??xkErU%ff{%b5Cp|+hLs{ z@*Ex~M^RGI*z_YaJE5YIf*<&Z|_ z{r%4gm5H$m=wg!`QFGrvTSE3-+1t^emDp&cA{U=-D#53}Q3fy|_%-IwGzvyMiw3p# zaX7O-!k#7)^KKG;+A1V|O@y)HGPM@>gtIS72QSN558<1!ko2Yd|G*}Y*ae|e!)zH*i_v(G4l@HxAOuuOOHgP#HY>7%SCY}*8KKyWCbNL5oOYJ za%?(PRtNB*b?jZCxM*o+E2Z&~G zyzNe7rKYHs4rNm)piFto=w&Y~DbsBJ_A<-K#tW9rJ6{`ldc{k6ci=>o@p`&XZZ`(+^(#o-DK4t+XZkzR9ta#m-uq4NkJy zPD#wMc0E54qs=R*y>i+dt9y#_+M;(}^0z+vZPCo=?lY$=nG+-`VQa%P(pP}G-6Pp* z2_>qbP1XL0bOTgaggtGHFNv@B)((JZF+qJHz+!M_)o&c}a=iruS;w;M^X5|Pm3RX% zRn#{+Ci=K*%1BS=0Wo=S54KTME2gx^%JIU!tt#XGsu~=O)orHPr38z1;?t^{MMW7f zrV*nVR#Aykh1m-cJ^@R?I`CPFhsJG^;5C~)00a_*EAxsu0805PdJN5b%xlt7?ckVa z=MLe;{{5}0f%b{pyn40OfRjKHu)M$Ick&j@VK?0eIf$p))VdYIpX;8_$LrR!&COTS zv&Lpc0M?uj!rZ1ZQff=t5wZLEaz?l|J5Ok$p`VjFHr{#QAb3K~cFIF_1)yQER_1UB zIxkIOc)|84#kNQOBG$JZUsVKlzNiSyqV6E8c2kM%0=h!G$xPLUq;;;O%HKqyjC!c< z5IMl_E7A{F^--g-68zI$Ra&@-n%%YyLdX%0?j_QOyLEp^KdsB>*DjF-pba}wGY+uU z)NY7X0{I6Xd=|9)Tx^}G%AAsltzptpp`b!d1s%juQTaGDN3E*Lzdgh23wOzME9IWf z+{M30CtgdrIZzbE%fCg3{o3+E5#Aq5wG$Z-%i14kb95Y)|}ioob%N+4YIPc6pS&LKdWjP8_>I| zx^Pi|8Z{mmid+ItVfkf{uJr09e{rOM?qAW7Wt?7&8^|)v$Q?#V^e*;sv}oBS@iG#j zwf?p4ftRS}8Wulb!@-CvK$sUpMB%cV^T{1*nEb*;cB{J|pRm_peg`OUtdAcdCFa-r z>pZLWV-AovxUaM3+a#&tXOrTC605HzTbJP>Mem-06b`DgA)?(6U$;~l8h9u1z}Uza z_2nR=^I|7@Q=?Ra)i+$IGWB(YtwS5K?Y+dBuzf&!YgbYGSp|ZZ(%7hNn_fhmZHI@P zy4;&{#`4bJ{Wbgc_M+vfIcLO=z&_PlJBs!eC$~jCO)P$!tS@CI5{OsC){cWG)Jqo2 z{@OXFR#^mlOWEDdmkUhRmysa5W=w1&35aT6&zs+059s=k1vVzO&4t<5^999Nwu#mX zuu9*NjTxpd?tTQXHMA^sibB{wnrcOBgQaATIrsaB;S2EQri3@{{BXyecYey=MlojcHa~R#J0x?Aps6AX!O1uor7wu_ z95I%A3)y(T+?$QvnrY?Bz3t1-XX|eK+_%rGs!W8Fk5%Wq60h~1?wwa!+_*bsZT5TT zMT+f5YTfBz0fgbpCvIP!TBok<-K-gdcl|v=y>TD&zTPyIL_{=-6 z6Ox_3kKE4kR^R7+wUmXk#L?OX%jwjay4muvZ;JJ6kQ<%ssqp;hiD&&HTB`0HXJ0+s zz2JLeu5MNviUpMV|PtI6mc7v1%Z9 zKdk;u%iSe1XL|9j&RUzZ&SVtNa8_vhQ7)vPzJB_dpQ9g^s5=jAW73^!yf4*wBLfel z19NLU5&f?In#jrW(8a8%zo);!m1n*6*d?%gXnjf69+`@`~;2 zcfW|68^hk7aYQ8Ct>Y}ZN8^^%PgX#fsl;uu14Q*2@1vW_T6(H+OWn@Iq*&i!e2=pqpW+0#;9iJShP#q#l*$1K$(ppav6=zC z-P?c?O?L}jUNvv`6ukkjvO$>%$hS^k+*wVwK ziN!7-;48s1eq3zT0MAr6JN-1lC___fzbRXCtESokM#K4Axak5PuTwsQ@HbN|_T})9 zRAQTyo8c}+^X|A1nBM9=o-J;w@w=Vp0*;Fx_VKdcT*O6V)t0E0!`(WJjLImrdQb9G zz<5y3<2&EuwKj2N@;ZdL(XPhVhPz~4U05MW=@ZMbjY`EIU>+EwSkXg0=fM3N2B8V7dd46c$5 zjhs=M%#`|=cQ;Y)nCOFbn6?mjp! zcF$qs*t-o>i_)TnyAlA8?I5P#2P9#AP8l|Kd282FJ*mb!?RS@|aP^(kbco=sWjHN! z>|Uj2YrNC;)muI5%%a=ONeovUvI+S+hZ*Qihd zUISR%$c_;C-Hl&z^;&Y-#8;+qAKo_7g^}WmZcJUYF}ZpN#rwRX$-MPH2u5+LaaX4C zSw+=_gsNYeX9h>8Z&%dZKxIf=PeepZtDgnBHYK?}rYrNj;r0B6;>QUKde$Forj$@6 zOO);ZRiQiFy+h9a9`+L(x~q0XJd&s0#Gm(Xz#HK*sEf>*a@+IbY_xL2_?6}-*V*a_ zxo&p#(&XxWh&1WOhv{1Tq8&l6aW}2M)JCTlgJ0VEcLV{J^R~AOuih67cil;Z)I}T) zJA8)mo3rzGq{_n-hxr*}z-CCYOf9 zoY6eM;l9`Y|C%(^yNdz?Q3(ZMH$@)3Ega$O*;uo5GSJ`f`(Bnp?x8u{iFP=p#mso|UCS~BM%fAa?`psZ@x ze2CGXw{U2+6QBdhm6ZG#Z+_|IXKF+7QORwg+GsHGCi`be5s37s5uy&emE12+81w=k zyKK`uvaBtM@|!p>S+6&HUNhE1^V!}J+fR{a(I)+Rqwjl;3v~C%&VI)-D{^j@50FYg z_M#pO&c#Yi>9ka^BwM09F6wbEAEj1ryb=nlsS1WxyqybZjMjX48L(=TUZWpb0&ynkJd(>*UZ@b`DEL;kP4V$!yK>63cTW< zicMow@EYHYZ3LI7`51E|WUzGO0qv8G-GyhfImUT{Bl}nnaK}`pL}Q7tF+co@2q1n+ za`l^NtaItM*mS?EvcL!)Zqt1>Mv7svastg%F{m9O`2?NM$^AYd=QGy%nDbp8<6`9K z2fc0eQ=v>rgO7;KzcQEqZkzw-a``711a0Nz2SI!BUgMi|C9m;dG`3eLEjr1sE4#<| zFJKlmPgxK_q!BrM>aP8-WnTl*}71<>6#ORZf!lm3|k^Qx0~o^EzNCi6LE7WDSy!(jf4 z3T*NeXyH;VhjNzd55n3k`G>pNeUi14FT3ZQ5mwmrMa@gL64=?yX5#Ug#)AsjzZWd> zkzp)!^{f5yz+pfC&Dblp%CE4*r8(niL|Oo0O@2FS);pk<{0>y+D3VFZgS``DZ;~+G zZQ&TP4X9$XR&1z%F|j@ZqvnN9>uuY(6qr%-i5x^%G?ABgFmY6y8J#O)qJK-h-|DP_ zSkk-QsWgE<&TKehP*2jjT#_Jnto5T4MOWx7oGps1y`gY@+%i{~}B-VR9(d0Ex@0>nLbLGS*>ueF?ACHy4T6n$k zNq#Z!7g00XIJHOo2<IE_v8BZ0p0N5^5^oci+%+Ozz@P3K7H=-#I*-0PnCtwo-^XVy|LlCs zuM(EC6>^=mF379DkUSp}f@qA-awD`i0&I@u;#|z?a30Ra)Ijq`+l&|Wcy)A9w8Xzg z#ln_4^3}GTHO!|P6lZ}UhUI;3dv_kpw3H~!r#<{M5nV5G7_Oo$a3z=&8Ruos&|6D;aDvNHosi#_@0)H`a2ppMBCV3dO%20z{H7wVfRVuT)%{E z=53cSk4wHHh#5?s+sj=Ty8bl~AF)aA)bP|)z3owp<1$SlDm#)}Z=RG!r59UFrS*=> z`ITG0TST3s#k{y8e)a!l@BQPWEUv}>O|l^iEbM{-qXio^DjK!45lf8G1WdqcAP}>m ztw4LLG&lWaWf$lzXyPWy@|c!;Z|}XW?Y%9<_S$Nzy=^J|rGY>KVl7y!Sh1qgcH+i1 zR!o8h_VYexo@cWO0lnYP>-+udYfJWd=9xKj=FFKhXU?2CBb^phk<;jdRU+m`oFyPh zfW{`#K6bYMYmO4u#Z&uV5pa6>J{jGhbRoxy)NuMHyJ%=(^(A|q-o&IE!OmWVn3z<{ zbLk|Rl4YET^kS{Vh+bbdNBAQebvEReorr#>bdt0wdTTrdd90Q&Z8pNee3dnS#N1^? zXXe+#bf(wD%hipyy=^FCXh+)H<#mZ}w>LgO^HCwzOT)w5ZmnA5EnC3HwZ9g?p|1V4 z%Cn!jzDnLo`LLMIj)?WFDzH@=iFgyiNTRDtH@-HHtyDxCSGZ#N*TM7`kf6>^d#kH! ze6&Ozjs&OTWyF2CvrdQ4jZg*~X(m2GXz*F6U()z&m_LkQb{E1%>G(L}biQu-La?8D4pY+1avbUW-f+z<^yQ_}M(2F#zD!~#}Z({_W!2Czd z%H>EQ6!yidn32M&QC-2Y1C_b6_*`RM=!ke=r6% zE9WRACG>G7s& zk-J0X)Nt9rvbMc2zue!=#$r3h+0+4y)HUHm%~x48huVHkPYWS81i4&k`5v~bu;8w0 z*=}-uz8^g5iq25$zmrY8KO*?_qkUjsb85bdBUAkhpv0DlbuC)vRPE80bw+0krT5S> zTmpV9yAvE8TfS#Ul6BxApZLFW!M6SSZ}#g*fYFEJ8@T8czhK3`&ycJCwo-(T zXWC1c;)CmALGx#{370+E{bB+1CCV3ybT^;ILz3x7#-c<)9o91HP-ms~vMx3bikUI3 z!Zl;QDqO>wg)ZhVa!`38&yFTvaW}0dZdfOA!>}G)N?k(6Cl_&z^>d=Sm=CK{ruF7L ziDxtUqcP1N>CMYap$vNtC8@-QU1bB&U}?Fmh%pFKjdTy1W|OjHz}2_xJh7REv{&tN zXAMiCP-`=VoaKl6tDf{nZ*{76x%`bNDA1fF@te;-VqGECJXC(5_(xY*R#l2a+`$IBy$(*(kaD;Yyf<=yWUID-u617f! zcnKM}eos_GX8*A{NcpD#Dm5NN)>}2hIoN1;!C78(UUroIe-X3VAP)15`T95{zVm&J zMZ8GjY<{;kBJ6pIZy5A#{WQpIkwm8u`a$Hf`U;J5B7BX+2E&KozR>aiJQd{f_+w&h2~d7WkptJRthkQ*}C0y07U z(IuLhiqE0&AcnkwgCmz4cD^X8i>#6jaBT&X=pEfvhcH8kM1kwo530TDkj7|?{sS_G z^>%jj2DB|#POv-73~@yIgv)>%%sn}lH%u-UEf*4R zv+-|fh!fHfhlK~ZorU2g=1FB$)`g+A7>`bsY@gOT8)OPu5?<;o&*v1=_1Q|j9=VB> z0~n#U?@Cux1^O#woAqnjFF0$ZoxTRlXE!}Re{Z}_iZP#hk?3R)7Q_aO=KGh6s)sxsI5bh0q-GE2mp7Ve#_TWs_|oi z*!l4dGvrJISY#uH^AS%dOd?S0XLQpvY^!$)86n%bLYE4$b)88r+=3;a^l*2D@V+l8 z^k!`v4e(3R*nIGXKd;u+_Lc1mQxu~WiIB4op@fWeYW+){@sTCwD=J`ul96nQQ0#?n zrYJyS84r4EnnJ0+Cd@r>&Xn4vjTH=Y*K@e>bJAdBO4wQG$00UX!FfS`Gc4kn@_AjA zx4ucsXE}($tUWAzFHrkSM|hP}J9(A!IQ}w&sZ}k<_CFrGwE5+{=C|@z7dgK4#2q_R z#fd8v|M%V&uD(M^K4yt<#4NIECIo+PLbne5bp60TWSr+EuA3 z+u6aEJNclVD!+AzlOD@RS9nT#?4##s(S%^iW;#tfWVXquu3+bFv|tG9RBLfsVtA@>eS(9 z@{Yq{cM+oRLEdW{8)3jDruWmzdr&ApVprDplFpGqO%FESKg^~(G-;S@+TV2cW7?|a z0mSbrX5nSd9BJ^PxFuzD;i}S=!L@VwJ9*6qoK8aCu=ffA=5k$)A)lg`uIgS8WEZ-7 z0jHSVuJcAvyt_d6EMnW0%ow+%+w8<5>l=+9f>Q&^N4Yh&CaMBWsdh=gI>5jp0wB#R zbjDHB*`-y1=&g-^P512HQterGRa(ktqs<_)ee0(;{ym-H0h@ucm&1DJ4CmT#&K4<< z%9X$ESL~ieqd_AvuDe=$2auWLbz^$GmUN^?ESvF1Sohm;D~1R*XvU+fjrRuO7gx3X z+^W^FiEf&+BYEqpmM=(1fvISv;Ok4;Scw@L?xTv$G(qh{aoeyctC|3Z4Fb-%vK`S9 z=DtAbh9j|@MC=H=yUxxJk#dAZUxiNJ=T{v$$+wD+;vCDuG5HGgm`eNtx2@ewNTBf> zJ!p4jQ?~hs=g@4crEh1+)^O~+?&c?;$`W$1UtR5Qd;q?(u@6jtqx4dcRI+b^1c|I< zT-S869u3NT7$mj5eyDiI(zwZO;t=N1oX~(Eh`rzj4JER#{oMZ2k>kw0EKu*cxhAaDXQJ#V|+u#OQFEW*vQ!G zzKge4lTY8+qJBgN*zBDrQkp+wQEk^L+H|-5+=_^K?x{#@oHI>;_0PLRCXr}Pd@o^V zW;ijN=S0t4B2sXv`RKRc=5jIB$QH$Twi@LzdVNVpX|AM3CvS1>Tw>u#PV+JZw@60x zUoo`M3k6LnN6mh<_QMkMdQz=kP8V#QMo{%7_HP2RQz%>BtO@h`D=rB{1M%NxA+9&b zgzPL2%TC8H=zGM&q=?y__9dSuAGYv8m6I80Y6VN$o?jYj{UK=*Y~!E4tt%vGO1buQ zxrfm?T;a_@qYG4rVUK!fpj*U#?3xU4AhooC7=@3hnXG`ui8@jC9O3Jp;{?TsNa-%f z?ZEJ@EL}ciFBQ}|Vho5YEJP0vgY1%NJUttze02ANt0LyN>8&6;vqMPLWHG7L%fWTC z=zd*TPK}uRy{ztrukr=?y(AaAV^RSdCrQUx!3=R_*A`<|#*22>+a zOwZ;uaqI^Gtty!d)M=f=u@Td0DNLkSVZzwcB7@Ds6CdvUG#PdZ@4sLlL7pZ&#?zhe(8>Cc$&F^-j; z2(`-IN9hM+<4e~DLaqP9OM~%Q3pGZ)mSfG!?wi1kHL+kMbtbxH@($c%V5AaBG2QA4 zD6(r}#N20JoW2NIWTEsu91bbM(E5suxa(SuiT)9rgx=zU3nbsya%}ULP)epYeh&+i zRg>OdqVtm0@Kg40R3T|Kb(}4ZiTFuYWKJ4IAf^D37d%NQog3aTW(`n6gxmm{i*%Qz3MWM*?n3c@{0Ak z*bo0S(zxj2!*}h!%jh2u4-bgjf3@a!r!8M=e(Zi9b^mL93oEU^ zS3TaNp)Kq9Mnf)6?dR|LTcY;$Y<*>U$?npAOLBV${Bqnx7=Kio%7Cb3gG}S>hg!4m zX?jg9FkH`RvNN)36MM2yp}fZO%Rw^euKq2tfWnY5tL`nURb2_!Y8AC+DWQTvbb0(5 zb%`)|UC=AIJMrLV`5bkVWfJvA!`mUPjP>nwrTlJ@j0vt3AC%uk#5+IAgPdteR0p;* zPBb%|B)@44hw+o^HGm@MB5$b)62XGm1V5_@+6T(H^oWkDHCqVs%dD3Mr4KGYI+I*> z^NGBHh4WF{geYLc_5!ZepFDarh`Rnk$)}a|V76Dcyrdi$_i}oJ^AC z&Af~gORKi*qBbp};WSR9t2;fUoy`kl<>ZdPGnX>T1Y5|%^sa>V&R zKCUw@S^XOel7Ai98}9u}xL|q|9C7&q@B~BDVLeI5aW=U&NtDvtPL*GBqRdiE?!OIk zR!IyNj35vT%P3jOJk#ivMt+T|Pzp6<@ZG=x3bJ|z>{5>D>nUTYe5J~;QH3qOa4-uut{QRx$0vF!q9Qw+U7n(geAG!|xtr zmS(btD~k|LV4!Pt5}$&S(v4g&@e%Y$LaKmakzAGuB&Ey@%(nBi*22ueLVJLPt%8fD zIp3%m2-VM)w7r{8zzg7p#zCs6HMJJ}rSMC>f{vJnJba_m7c?~bjI?i-O_P(?VI~dx zOI`9GT5`$rb&X#|s+d}t{GuAMkHb+nN6cSVc)XOeaak62kb+2K5ReU~%7SzbvX3Xc zl)m&t;nlkUQ#xzg#qKAdLjhrc@@wlG^A)X1g%Q?(SXXC6@*x5c$5vNgJR+%i{lzD4 z#yKK0;Li-eh;WLS3+=ky9$*_mQI-E0DfJpJ!RZw|`*Fdt79SDFjdGMq&rz9Y7pbNL zLkxe`b6$+9V)Qfi4Ft3f5GpT4G0%08&@83R0s1F&jzJ`G*oO{8>um+kMj@2J7HJ zm#jCUxj#q3F%tOSVpd{dE+44;yX3Vh^jQO4GEAdWETBd%4(&)y`>GgjM2{Yx_FVh~ zyB&X1?7$69m!=S}{fQISg(=;#dJaI@9o~U%!625a_st;h_A*0@wMV}SpFsGjr~ zG!gT;&`k--GVx!P+Tl!E3YHo4{Cv8s($fz;{|J#rB9xK8^5pzao+>}wooc8xlhZ}? zZqMbMc+X%FIl|x@=n_*itM}i+RBSBqr#{?N-#|Bqba~hqz3vQH`5E{w?Jf<`Yt0d*M9}BfAMY*Xz;msc zl!n1bmTe8#77E~~9bxbIaH4*wz2-0gcT=3Mo-Uf|1xTg$dAiJx2yf0cH{soeTn|4rs^J)9hL^TH1p+vRa{dLh9=3RMeTIbbc zFJ8`gR|;pm=ZoH+@vf?B#{1~}s&m$tJjG%Jne#wJG*`}L_#P0`&Eplg3SJn0oRc;( z4`x*eFVI&@YL~hi_K&~7_*I42{X|Z9#M*$Cnj;W;tPYj&grpUZ5A!h_s=2MBAxiAP z`=PLint;!I5?dXjlMcW~8@TjbgZaD0sS7)O+kMSAZZcWGrrCDHr`2b7ykc+2@qkC7 z6I0V3u;Uci6Ft}p7s!!lo>@g8qGYc5n+^{qZ&4*NBY{h%eRMyQqV?ArwvIAyuo_}Z zBVzu!N5;NjJszF(A!t2x` zDGDbA2D(J`ja{HZ6m8)r)LiTInfY!mnHPm|L^9!5G02WcgNazA#C+C8=M!v~z2+=6 zf{6jl-6f%nn%Xbajp$o#P0{7j48#~L^e{0kXa;C3OjR*6RtTtE-91i=y?qs0dZkp& z$Hu;EnbnbLT4>&uDn%Z~43z-Yb96Tvzn_8rxbCI#unxnv-- zh*`mpjPB?m|57twt(h~@1+@2+-qxOW{$JAlHyL?l<(eHnvvd;~M$BaK91g6(;-6Np z&~W?0EQw`D`+dZ+TXFuL<<1W6)HHu|swM#t<&W)Y{@4dWEPrqjqaB+O^IZ+(#n`mN zGM3*c=fyn9rC62pGuP8ro0w)IZ@s&~9;N&Y8e#@_Nqdn^tgjcMZ|Jx5O}3UG-uLTk zO&03v$jOo{-uDmHn)jdX`wg|`rBi*E_5F>~kxMuyztoIJxlC(B9{ieG!y$xI*S`pR z!)d

ix7QVV}m|+LKQAy@b`l>AtT%^>_9!nm%Ib{~C6SG3ozN^hInDC)MD!|7PN` zobLN_&Yzv``wHlHs{R%FuNbUe?DGo&eeV!#Xp(y{cu{oXY7A* z%Jc~04GHgVe(lh0P|mHMv+4qbcsVlsT&FU?Gq!KEl5e7*8@UVPBd|FCX?Y*}-lRWB3K-iC@1XyHf%7r<^Rp z{jKV*WMzTa@cW37+k?!JU#HSPQDPKMNras#6Q3e3XQe+e&pLB;@;xy=alKo#yHcGR zx+~dN6+oRmKZ8d{<{{NdMYzRhk>7AOBh@|bQuKXjA+VTw!DU^ryTU^FynkYUH0(U7 zw%3T+Kp#LsS-^B|mb`(?ya6}wJwRn`otanKN-K4}VjCzpyq^M>u1XOH^bY)7&Raz| zOb)zI+jtR=dYPZDmzh&p=u2v>nAQ09*O06@@`>EB2m&&)itEti^vA;plycE?Z zdo~Yau^cQaH5&_!ls?G$JU9RMEK)yZcP;q}?Ur%u{ipg-Py|TLQJjX&6EbFSM)vg~ zY?wYt<4z7zxN;z;DqMk>J8E7>N7WB{!)&O# zLJSAKrt$J|rD8OcsFVeZV)PurrG?-%B!UZx{w~hO**awBb8qM98uW(bs*`G987|Ep zGH;;*D!gMmOYJLUgYoRNu0WL(~#V>TKH_ zNM36$%4|`O2tsnwEG6XVCVmw?dL)IXjzo--{&Xv^u9xNqGW*oSQU-FOhEuV2!(kZcV`Kqfv4o^b*08_h0gk6{e7hi5zI%g`su^5_6N5 zJj7?Wb=37j zsCdVC%J!P|%wYT~i<8cv>%Vb0v#>Zo`2fw&S15s=xeB&7W2E$&IVXX6cqAAB(M44( z{N;Wg@6fIJ4>y}__YqjAtcu`d+SfGg$0UfCnQW@r#;t>7Zr}9kn07hHlQzRr20`Js zg2D=rT62l5%oa!De4VjAJZ94n;G(0>^?puWh1x_KfNsyX^>=J$yw3`I6;Oc@%;-ru zffp=Y7zo{UwS{*QYG1y=QeC*%ipR7Ryw&|AGE>H*bcTvS`sB+x?}#MTMkzsD+2A%u zML}CZx?1goqi*N*+-aMIEy+$`whaP(80CdtS(j;!ngz_Fn$zMqipsk_ zL@+O_3WV;q1F6qEKms0V`1(=2vUFiE)Oxd}hKRF1^AGZZZA*lIpN@<}*K5O+r$|we zQEP%;GbG7!nq~1?^9x%*uoOUao&Z{S5}IO3NYpUN1>%SE0IxGz>r{))4wKXF-z|LD zW0}#4rp&H(%iFHDnFf0rZSh+3wl|B)glT@HeK(Y|hv8I4qwkuo?Hn{;^*&%IA2h#a zq6Z(5C@NM5b1GrCLInlQ1XPyH;De`Z3_z*?snkLS4k?N6k%(Xv7ZMMeWmKXQ_8`N} zsUKp_*7{_A?YfR=&%C7&>SNTB!67dSNac{<^OSQC@MX(rm}qJMGLNOd|5U%%N#YAA zO6jpA6C#P)#B!Z{c@3DmEdXujP-Z)={l`N==BhA3!88Yzt3>fMA^SUGj?0))AF-8r z5>DNSdf-dpZ~m%q63hI^@a0uf8|8Y5m`Obk4NYo8K`ADLUD^^ zpaS0N(OGjii?Z7h4an5?3ELJ4*kKCY=F}X~0c~@%T4@Y3A4zao>>&B=k}f40=Mh-6 z6oG+u4v~*2j~qdcqg*EZ|J-v>l*@-Rs`z!4D;|!D**nzwuYB`@Cm_UpDMr5l?GvC@ znE@!mzR&S*l)qeA=ik)xd*EtytigMkd-cXTQ)@nB!OJHGRi7?B6=Yv&)sWSHJuE#? zFDNKvxoxy^9YAx zE~8!Ar~^=7(Z(TdqbTMM(Mf6MQ^UHsM3>p?VdIJHsy-W4izp{hU*zUU*}B3-0r)DW z(^y+UORZqdGg$YzSkLgV7NLF;)=UE#SVOCT^tHc5aIf#nz#4!j(a@Czc=WPR_l4R* zinVuwp-1MNE%FJdiOO&)_X?i3$R9}%)VIo41?x%>Pv;y{%BYCZK_Z8m5;X=eW)R6Q zI;NfLjgZBpSUU3QSIn8Jm=GI96S^b;q&x{TV!+m5UkRGIh-z?gH3fZ|Atl7v9BR9O zCry)OKhd70DYDeqe~oYkq#sIT^%SNv@xYCFUZw{#G696x0an9n%BGx2F}zPt31%hj z^+2jtG}KczM3vXm<{XwWVg?AcS*1e8x^mkQW}hChLm(XBSHcw>m6o_;4?ULQd{p$h zFQNNG4{nvLIU>aS6m5X;jDohy?HAy6RrPW1l0&DG%d8kDF4yg7?DAI28{m3=AmF!9 zm7UCI@!D0@n=Qqp=Z=_*Mie{CEw)B08G$$B(8J~h_)ofUzoVnbXt_tCB;Wb1e9gbU$(u-ACvl0P^*eXhHa5%|ewJBC@42sd~_SR4bDe28ssn zH}HNEP){vKUysoa;qZS{s7E=2jdP5W(7dYfWfLpZDvJeSrK9F2JVFN+XN477zfKRf zpwb6K%)?_A{Q^aMMAbx4+@g#-%x89uU!+6tkjoPuK@XWfvfT6tddTeOE%Yc%E(dhq z5X5T^ne*vjWVnh1q`YjMhFV!(fvg6#3bmH%3!LIEzV2S2<4QCI+ZnK0Rd1>mXBs3m zGKAnp6!GJZ%}eBW-XL*gG&QF};PL=HD_3-{T-AYD6#%{e`GL33`+B6w3DP|mMWs-I65Mt-+VTFf~FxjArDuUX<{KL$^ zfb2C-$VX-ip=qYf^?HJGO#s!w`#BfT7rcZOTKME)pMjCk=Om08TtG5Go`lg=?~D;$ zY%zL)-CJpzoV`>OpOAzx)WQsmp4Uo8(P*~|NG9-;FnU0No|Z;(yigIi!39+BC0uoS zjI0tEFjde=JER5%vwC8{?DrDILH8YcX_r^w$0aW``JItFX2Khd@@F!T%l4qZ@p1D84!a;j~9?Ud1UfuU22~> zGPyx%e#&WH8YS=*xoj7N$Rka7-Np8NFX8Z**fM5KB@Ptnk>eU#Ml7df4J8%}k^Y{3 zLz4d%lB^Ut$!0XNysoAyXQV3;i_pmbM&=itsFJ0JXD87XeGe}$5O6=;#a5!=qigd( zyMLoDQAvn*44ZO>>lp<N|VEBeRV9?qtjqW0hx_bK3GFKLTMC{QtlH)6?(&efkeG?O66l20*}A zv&eA9VeTH?pH`=o{MzxbsyivB2i?g2R|^C74zy9Vo5|krsY+T9Bg@O6Qxsz=q{l#U zRQ^rxz|Wg1vPQD(=<*fWr~N+iD3e58?kk3ceeh^Ept+uaB79!5Bo=qOsAwx|T2Mjy2cyebN7Oy41V9 zJ#j<8nICYT6bB0R>M(D=fuV=Hl2CKEi9qIqa|35b0e0#}LuyD?Nj7Q)5Bdr+$fU|rDI9%ooIC!{#l09P9vcV(#h|Jd~sBJ`7 z44)U=5Xi9*_Gp_<1L5?zHU+>Gg!5sES@a)$TxiaHQQc<3<|DYGIbH7UZdi6LeYif2 zJ09zu9%T|aMdw!E{^8V^6mv<^$@w-+`|pM#R4^-p1;=W4Q$Sme@DlWMI+xIbO9g${ z=wlFGdck~+@tWBHZ{QC1w_6Ju5#?#r2~Ig(`oCzCa{^!2Ln8uRkKM^9HG4;z5pv#6 zUpsf2j-;jZR_Ex>ib7It>ksI)bXz-70OjQ7frgfU@|f$NMzZpU0?zFL=bwnaAraiq z3_Y4xyTE+vgF2Ena><<}$9izb8lsq0rq3+GOssGM!*Bw81EMv6s|OnOVC~7*jBN3T^&za47TU~I zpD$0{xdlv4_9^T>F{AzFVkJr1erv1mHD8vE6KO`laP^{0*aqpernSsUr_s0KH@*?S z9oqvq@fdv>Y+_qQe0rKWUbr=%%83>*oNy@#+>9nTS^GtIuDmp3vWu6?$8@s1NJyHd zWf{c_No@K^MnaJ!K$Ucjvm{|$M#2mdyV0r3VRsE?p{^ZIO3i587gFOEr`733u4=vPGWBU@s32waT`r7If2(X4WU6 zF>HJLNVZSCJh|&LrQPMSL3Y6y1~)fF=mRZlxrPjhFLy^6}qchF>exQ(KjXDSp4 zYkgy7(_i<5GR+@0q90qx=_$$EVD6FeWcQY(54VUPB-4roVhc!b(gx3WjW>B!88ga_ zR6LAr<%$lyAO;t=K!!EOqN8;&6{ftY5c{!g2A_TD zO*Zkku#~gD3m*Oz%uGBpGm&oF#dHOVa_88UN&^<4#3sg|4?{3RUtz0~V!9@USuwbf z<>SabrcQTS@|LY58a2wXxGEsdC9NCWYW{C=yiE}h95o&@4iEoF=Xg4NCM{H{vdZ&y?Jg1YNY;_3C5$~do6ZaEQ6nr#op){!5isO z;W4cibd736qo4frldbSH{b`x*7SK&Y9VV?ZKqmeklgj*_2|BaLsQhUw zs!4UiG#X-{t^Zcv1+)dJ_39^GR(w?KW?%p|y2dRT&xPQlpW?2(gp z0Vt68U~-%n(A5fSIZcNA%mGy>d8j1Iu#IOWhDi{zj8OM-gj6 zlH0!9zfO&wy_0i#fI5@&g0=zXzq~$Ir1Ka0`5UYtca-oO_F&mL^I}BSL82 zsxx44i+}~IOQc?6R_wwDntC<4@c|Lu=0`S3B1B0w8Z5|}vk(DIXz9j>$dlr7aQCCE z@_dAY0AtR1F5w7dfg;^qsT*_>nKYPK5>711XBL5jR0);ipIR_&|B_ux?cfEU2Dv(r z!<_oPE;^Y|Lh@#eyl|U9v^e7AIQvo(;pNW}=Z5@LodmypUNbpl(&}Mzp{6FTAu;I} zK-|C}GdW+p@5A

0_s&Q7&O$N-?_j5?ejjVADhYC@&Jl`I3l}=ls^IesO-P!K*%C zg2UCjwc}R0+pRs{)yZd3!;Q9vJ$zH>6W(&+VU)H5=*3&tEz(5G#^M&-47Hm4VyOLUgRn zcPfPwa!cn0Ju%`JG2)JybpKK!-xD|mUQSowY?cOaUbs=N#HFGFi2)WSbj@|L*tBuM z!i3Zq7Z%~RXhtGZxPrhc!}NxCUx#~%IkD6VqW3y^rPVXIW?_M!nAoA8?I~MS81pWs z12^e14@H`(^2Iqu*j~|s`ZO^6RhC7*!<9A4B64>P4LzGRb$jV^ z7&$4TGGG#9qgHZWVFBFY0r+nVAb%J@;BUvL) z^lVnu)f^C-yu^vjSV|WAHHeNXbc5dTppv|7@Q!T4-Ht08+#tUIyMt)+8K8*s>Qv8Y za2C<4ONxj>7Y2x>{_uFIoM8p8+uqZ_*KMS$rNRHJs)U#2pCzlbAEp;N>ojENI}uRz zEq4S;AXMnCA4?tQkaI@7<2L$-S@3HdW|t>(hzAiyq1IM%m98v`8Rv7$;>S-!566!m zkBxWk*(jwOmo-gIP0OdfwL;C*w9VK)+zO&+)!VdtCGeWD^u~tDoBN?vJ}fS3h$I#T z%^$=-&Jk<`kMpz505^q#gS#iAf;Ma>kwj20Pqq%XH?3dDB3xDjJ8m7qS94gkv}>cZ zDS&m*QCWLq1<>;n-^Ml^N7c|Xa3`HS;DCmnJg5S#rULUISdba_2y1gXOGtsW=~>i& zAlRc)asox9mDHiD@@~J7%-8dM8S-L$WWMyRh_Np3*Au z55e#=#O>$rCn^*lEN~8QuqNs?_akfNSK^SR@vC(4)AMzm8p+^B z^9p$&2?j2K{(BLJRfmVQRF?!rN&?Sw+mhgrB>|4i>Bd8hLqctTi#GnhX^uestv1W; z9Ix?jQI#AoOgDbKVw>6c!=O@YzRLveHh$G(ZsQf>BLe$;#rViD#;&cyJ*P0D(~fu; zA4y}(0RYjKz3zHb7mmGVW+ukxy1lc|!x&4vc2@%lSTA+qs2CZ+=%1C$<|K^V#q&wb zY+PoRRX*m;G(y%FG0imI!g$csl2M`e{7P9@ig>0}SxXZn21bWuX%vWWt+C&=7ws6N zQnUEiW3P|+w4SDoTA$TFYC|Vv3}VEYUA~ThuY*Y~Y1Uvf(zU||n99UQ)-1L`vQ*LK z*eG}>ud+v!2VWng08Y0r|6Q)fs1sAQ90yEOX}C4+df9cXYsxp*Ln%AltyM$H=_r$p z9ja&P>UIeao=2!lnlaXQ-Vhx!yH1?2rVWsn*0W9-BmanYQJvUXhiN4RlpZd=+8`?l zt#N-4cPm8jXbbmGev$6}QS=t^fz9HVBh6VS(Z@5^k@spZuZxz(^mqY#4^$I1Ro@Q{ zzoT0a4}`u#jA0ZL;u29~3aVFtm@$|?_zw*pM-Y;ya{ofY$9roxTL*%?lGMu#! zwdyaxbDk6zQ}YQ56hB|Od~gjBT;vdrDftc9#)gQw@^2tBzij#7?SIma6CH(6Ff_^0 z05fMgUCvzKj;}Q-j0H5%=`I_HCGEC0?Wg6>*bn3ijchq_T1dP+RUL2^`6uq>HlXnd zc}!&KazH7NO8B5l#(QyxO}DVQ{oAGLn6NKQ!w#ll6L02}Fh&0&kwWJQ8JW?Sq`S+LAwy+aPo?q*y$Os&ki zeOM+YyG;<(=zjsg(`M_pgjE7}7-zl@KcC`$OcDiUe_fyN>@z=w89;MVb=jMD^s(Gh z7ov$Bi7)zNbm6d{&t~|ufq-m5i2_>_E<3R%=yc`mC>spj^(7iG(9L|~i!F5y#9z+Q z5YVZjf7`hIz6qN<`133NJ;cAS@^2OKcM^G|p!DyKS|n7Y}Ussmed?leQv}?z7#sk0?utgLIFk#%W%+%p51=M z8YlQ;!V)2(g7Nh~_WNS~)J;DQBoEy3WwmX@cWLk>qJj|@p8s0D;1|%bu(Kq{aoEx& z0m9mf4C{O%GXDd!m(8C&J1w`-9hb996&R>cq+@H<*_R_}xZDpW<_|fmgU%NNf=#47 zxF){-%R~shDpKG6sc`D1FZ-9Tpfrp)Yu)?FRj2nwAaVFAH$1C+4N%PSe;Fq>3gF$P z?^k>|`OtPjH912OOHLDrO(_pTV7VrHRIL{&q7KN4*axC(F%~L>Sw^ z0Tx+xbm@I^=DhO|`gCVvVScBXCNB%~|8ei}`8mFp9i9Jmp^tP&gRR}k!W$t{l4`oG zDa)853#E_y9V_Y2ATaVHiRy(-{=}jiyQ?K!JabCzx?W;kH(v8Acqm#_NTkUrHJMoebVVpzLps+u7DX~R5Z}w zmOzqA5n(_Z5|njX1;w%4Ou$QKubn01<%1Vs>x+)VQGy3oZA`7XF%?-z)n>l*8h1?g zB2LjlkECKKes?Nf0l7XY6+2>Bj|rNT6+{WKT+??;LRr%e6Z(zo6Qr7m)FV&k$AwT> z0xk0z8Y6TDE<^nx!~Qo#s7K*brhrt~8F9lrX7 z&6-*hHoX>7TgQexEs+dk$G`-G3UNJ>OnTBHp|R>j{$Ya3cLrMis`#A9jdz_**meu0 z!Hk<_D>;a@dPXhWJ)^-~hUO|`&u|0bC-plqa97w8&Tbj!plfEb>ns6u`421k^T$2KMp4|8T+FyeJoq z5QwjP2=J<3?>gKN`8iSqTVVT!!*4tY+|4pd#)Te4`n`!TxPEh`P1ss>c=b_dht#0B z>($qo`ynRdX5avotYrL9NjCSOb(0MV=eNqrrh@wRby+2^Jt_jObk43=l9RIreSQPs5X7|w-ySVx7+0h9^>?;)8fAZ{2c~`HC${i1d()z2H z#RBpy#u$MTj-5gN*ksM+=c|7K)v0J@nT|}R`@`{aX-AfA2tL>^5!`dEhKLz75PEc9 zyk~Z@(kpZZt=CxG%spUZS?%u z`2$_l=ZjtMbj1&6b4%xO-kR59U5#DYg6~!8&NY{tZ?oBE^L6`>>b__mYfx4v`ELB+ zykAS9WUi#%=~k4TD0RPP54_;)rkgDSy$H{GD8b%C#TfKl2!sGLkz<@H6A- zTx5p_vx3|p3~jJRe#7pLDG`!f)oZ~7Oe>hs8Cu+YQ`o>8bMak6k%l_CUfW9ifdkjS zauwd?Lo&o(98Po&6xR=}xxjgwG4T+8A7?^1(>dV0$U5{D{~OaqFL4YweGo&9>ABEYegSu+=uA(96XS$;`i{MK9w>4)v z5yq@Vo0W?H`}XI!bdVUHA3G! zclh~bibt;dSofzyHz|1yj{3TF87RH%S9ExE65qq3i}qZMl-Mq5rMZvrH#V&_S6CvZ zImnRek5ne>>wyF7rw5Nb+oL8AGF74zVCFkoKSq)q7-QW$|Hdc}f1VHcCtjzs7Fnq#%E{`uVftr9jT(iAez-%~d$=Q|oxs z2_PiR8ngaVaj}1z$zW?Qte2X$Umg!pgNg9Td=|-PSbzDwx(zss#!TbHp|-E` zm{l%-;wQ4`u@l*`i=0V)G931DX#=;eAQn;wbg#b9eKmiC4K-z|m$J9sro=*kiA^xy zV~ffB9*KvF3(%Zcv6qvZI@x&TGWryfW7?Qii3Tpg-MRI%Z53VQHGJYiZE=t63e(0v zn750dqDa{Ss~cOEb47}%*WbFa#dUNLWA zr-GZv%SH3}bKbnoPBswF2HZ4@*QEGPzakd=IumSlap@&&kiEi3Z&lL(rZu04QoU(|cO9$o$<#IVfXwNSo zOTLcfd><+=8gBt^$p1-3FVk#J6*BxUF5AsbuDhK*&VD(cB~yDzu}L4*yLi4oD<_NU zk~gD`=C8dHNZbzd8`Ov2IJ;F-&M$s^2~EjQtqz;jEa6yp0k=|glSRcw%NEv|f4^9V zo1}h7*sO*y*5Qp=E~ag-h1xy=^U}^)R?-AT(i9Xw)qF!RT)Z2FyeRqSbV{L{^0ZBH z-@3*o_m?=X1k<0A+|StWmfRxUdbx^KG*_Nw)rk3qCsvLelpCn>C|or*g`bzMs0p^b zB)bOKldFWrv*GP<*?#D}-+A2GDRkc8bKa0ca5;a_Q+*ec;o^FKR=uxBEVDE#TuKM7 z{RpyVO1){e9K*Shw!8Uk`v6s%TcWz$KKOg24nblrQtxuPW;*)0oP!qwr>txBH7?<5 zf`!GTC^1VzkLJ|ZntN2KK%%Fjb@RC$K1J6E>$G*;DHDO3tEi5XuWc8M**|H3W{gU_ z{_!f^5aiMKhgULz!Jr>$lGq~e$VB!vIM$Vwp0qc=429>gvN{cxZ9cO7hib~l^ zrKC`4z(P13m9pzho(m_FN&rzR{WJ~X|DH;SbC*ijtEd>J(qgX`s5IYg^*d0BVJUXH zLf7sa!S2f#Kw%$Y{jD|Pm@Vd~Q<;A6qVFK0Yx=eOnhlXU+0xsDNCI962$AGj;j`WJ9~pd!@vqWoq>-{Wq+%GYqH zb+5FbI*=IGyefEtw363?*?5H&{f$3v&Vgzb*Rcy*PHe|^ip}H1L4ISC8q6EYen{sS zyN2$8SF)?vUI19;YC6ENpw8mGK=@>QZaC7OYDZDomNl>HYVt8~XZ$!sw)? z>?SU}en@Ay;3lM|vna4JD>+^7Zo&+jU&r-M?OwsMWOQ8fqF^C*5u-qGngx-|CQ9xUDaWK_S>JQ?T=wDNR>RFSiYy{W0a99;)8|Jv*Ls2M`y$bi=*$2 z4_+2MFFrUsdO^=Mg3!L~o-6gYx#x2I&FZ;OfBik@=x?BBy8Lb!yd+lCUWMH!nWyu1 zqG~_$Ui&dB9yr*%k6rZrJ-xheUgT4ICrHih*9G=V>NBKvR~f-jRU(CTzQX#Z2g?Yo zAJ~l2hMp}va|(C_DAVJRY8!QZbY^_8G1U6+q{Ih59cuj-eOMc6m5DleEB(_v_sUqP zd0o~OvGMV$V0QCwo0E&&4(hJjLoTihuy)U?K|8U`3_V&yjHV;CktOElaaK501?gt( z-i(jep7gO59Uxy;8C6HzO~>Yag~>lQ?+YiNYyNE@`Lv>K=En!;gzj0%>>k>-@ti|! zGZnS}fo>h>oE<>B&mrqTXP8GJPpI|P99AX&sg?O_+uI&u*;s2{y~v71aR6+8o*Xa( zUF7Q#&noyso|~%%3!C><*dm-~WlyL#)ZP#0(i6b*^&I0lqgL{ygwOAc=IE(P$UtAG za^1e}wOEB(*H8z8GqAT@~&0hYpX=dRB3NcOaVY0e@`nfxmHwM9&QJ zyS*5CY_6nCAZ1G*D=8K54(CaG?RC>^DD=FhEk8IxNVBgZd8<}+uB{5^xc<(6 z_QXWMqvkJc5&FW{-`O1fK=W_2lb1m~N$9S60?y^v#~2yJEim~R$_XaPKLQ7m_>$WMflK;3Dp-dzdM_=yI{rV=DD-E zVxE(+qVp{vCuv>2*=i+cxj!q=CkRFtt<$zytcX8Fz z{hg?42V+xJ6sBSan+lCYCynn4yD3k%SM{@hWYhXP1JN`4JN+5?XPf2rTYSSFU-bI6 z)EyXcd)pU_q|loh4z<_x$?!);aB+LV`2w76r#s%)UUOtg1b@-KCFTYT0^eO}=HA%e zw8#59fL*1m%RqDz4mfoCWO7Vqr^I7LZ2w{@%CARZ)T2>O_hM)`rJkFE z|C4;^*6|IDFf**jjadNawEF70(4!o27VDK)>}4%R#eOG%1qPS1$|2nhkk>#W5bp^% zS-5J&=k0GQ*rI({yb0X^bkoI?o7I`*wHU7Dl z{BG-%;ksz3t+~@2Lvm|jfjk8W2dSz%SLzZ1b*3TTNV?j_Z`A5v(y?W$pPkNR-7TW; z^{P+Toaq&k6c30py$F*~HJk}6*V-bX)_<0fLvOJKG}UX7S8wzmo$4ZnD4X_Q`Los5 zt7qBwR_}ebeUEzYbM5;r-aEEbDSwmqew}^4&b~)XnSEdZAQ-viTtP3a5yUXHmGmb9 z+rxY-4WRpV1ggu$&E@vjRqw5cGm=q8S4 z<1oHm(q{J2AZOB1{^sl}+jmEiv+q-TgU)BMi3aC)UpHxpOg`r_{eGF`Elb|{lCxr& z<10s9lrvJ*%i?57t|(^@!M?Z1`=WAs*S@#P`!(hN>b3b(>$(tOHTvj+>I=d{mH{b~DWDjX@5l z^@+$RogSSU?+QAThG+$C%-O?HpjVx7#XGV7>jlI@bM^?{$4G-KW!V{e7@JhYT=|M+ zKsiMBo_JSI{Le$}!E?&?t(jcB&v`q(Bd4T8w=pRE5gn$iSH%Tn%aQb6Q{$yM-0Daa}i9m?MyurZr76c`6dv1fOJXaUa!Q85Z%1Yi1~2WtYF~#M;-{F=cCO z%Z(t~)*;LO*uutTyriZTc(O)pzcN2HHj?g?&mcXLdUoqgS|nP-Ihv_Unz9<#@eZX} zMny|i;%yb6@xkoaN;v#dW;}LD#RLG#MVbH5NNUNci>b!-JPCA30n$tl`n|jh;T3+K zxZLrgZn zm)19Bn}0=<5n;|kL=(&F#h9x_nzACP-?2_$MgNVLlX|J+k<`>PD}=wDIfZ;INwv@T^=5&SL(6BxQ&YI& zIW>s^k{TbKMBY@1Imd!u(2|-*kF0;v1G7b-lEqdg2U_pjJHO>GqAMwE3Yv^5nD57U#9!F|WZavUNw? zVa7r11|#e)=-c0zbFEdQyc5c$98&iJNZGMAB&@E?vS6LN@$7 zsn^-l{8CQyvFzAY-&2E8iEA)wd;X4Db+rQFn`*%fmUYG^3!cr5G+1w8KB72MM|Cz zJxW}F60S1-cyvm9@b>5#@xisx_aN9l&-nhuc>W}(avpt703jBCGAlXx7kUMr`<|_B z%GuLgijgjh%%X;f4J-8M#qs0U-8nw`@%ZuL=+()R`0=IDPsEScMQ@8AzbU#Qe*7Oo ztun>OkAEW6Dq=H!{Im9ws`&9}sP&tqfW-a$^zY1$$`tTbo|_-fZh5j<9$r4de5+T3 z^zW=tZ@@1$KVFes!_*`K^|NY}Y<@hPyv0qq*-a@-F759MYo_a)pA09j>+j;OWQvqE zKiQnTl1*uOxm*{IEl>I?Ai%k9;#oHF&#A{)Gz7hJIn7U2R0;vwb}B5{YuSJk!{R`M%RB#{k*HNZ6M?Kl0?(an^D~4Mz2}aE z#WE6N7mgkY|6()gFqs}PN&5;@m+1gnvo|>%d+6I7u)sr|;H!z8?)f?ZsNhkXS@s1# z+S#9*3u`xH3@jH%2LI~(?%XHfPa#8h?tXdl`PIGumxrgyWV}nHS_y*e{6@)>TJ2}@ zgmd#7ea##n_1U*^dIZB4?+muQeRvup`iU(EDNerW&TKblt(F_-bTN0ns&c}w1493Z zFLVLo9#Ja2Q|e5T@`=D!`9VW=_G}g3j@&+`hxR3bvK?zCCZ^y&vXjTPZ+83SKV?bPfWyon@S0CT3Vw@xWWjMo!;@_su=9hDt(YN!RP+~If)WA-hnNSRpyPYd7yx1ew zxu$HIc(z3UvHf=Z(^T+g#cp-3axRbmX~IyfGu74|YfUC)eOedE%|YNaJPL(U z7x?Dsz2!c-^*kEH0ZmOo1srOIAw@^U)1%12pz&K?2sUh#x9k>Vi=S%wlb}2Brlhf$dQU%F({EPi z^o1UNv+PL_eq9iTeC$-aJgZ|rHtg+*Qp(EF{5#W3BQg;!@DO+ z&-tSz@e_gQq&@x{JqF8)~&V@TQ%d znZwV-Nut&8ndI}-UgPh1j#p;#0ypk(_dExL%^QMTib+0Kqnbp)Nd+IUB>virL1At~)l z)9=%Ck$d3Im((LUYz2yo%?1~DUkboqE)hY`@v&h zpU_Ymj!vZCD$6DR?XQ>S|3;GA{lAgs#x8a?AL5mR9M_?{zg3%IQ@wsm^YO1wSh5tY z_6QpGO6n8^&Y31&YmBCn{`%&te9^O;uPTpbRXcBG6wNunm)M`%{XZ4Wuh#r0=SQ~| z%96ltx8kWb-dz3%?Mr8-z++=Ky25!o0|stE;pVdP=(S7C3W`%QQ6*a^QuK}IY|+g8 z8NsD};(`+2?N=X^97G4uW8dzsk{0WsHff6WMu;>tN;sxR*AXODCC(Kzb&I&@CFabQ zsc|lchMYN)z|pbxnxWbyvKWt;IzVC5=A-MKCQnH+rlemj4l5R2XmtE5C z|BqUnl^OYN(S?cf0xDU#HJ`tFKnpc3zm6Uxo{FgD!anurdj&pD!}4Euw){jVlA|Eb zH-93#`I_^hRVncw$=x)4W4L0|^y<*IxBBn-Fx~Bs2Hh{`Y%Hj#ZhosUI==O(==kQh z#>bv$eJYl-C|O6r#a$ICU-J5D0afx#>9S4J$u=vr?WxxL1da=49I&Y&qcSNHdrA}J ze(ef*&8jX*HD7~%_(ugKmF7x?In~M6HJyysb8AF9Xi{M+Ta~tw*P5@nIhI#l#ZUB@ zz&w9ow=_I<7MfYGJ(AUY)p;?$^LG^D=h4~UlNxyOz-!L$YucWQPN6-Za&v|A`&1SQ zvA=0ss-;r|m2yyt&P7MOoQ!9Mw*8?$ZYed+Hce@m4AJ!dyX=RY>Qq)LtGegoNJ9(U z2MK!4;#GSFZzX!8I~)8Y`c0t-@KknZcW(4;J2%mZtWQqFrrIQFNNQRye3qORA3Pol zXCz->NLGVz`Tf|CqGy#qV1IsUf41olbH&-)r_fKyGq(F#3gW;sRo-&9`}yf?DCu}? z8NK%N3Tk@e!S6rT2qIf=mOmfk&w~#>xYBaJgmq{-%Us9w#)yzpp9`c>jM2W*Ig%eg znX^p0{ftKb9Z*MGn{MtS8(&tZzc_qx_sh07Q?PWltJ`-$Rc5tzM9&Ltn-bdAQMU8;aPubZme97! zta)WSSNGwOh%z2~R@J@cjq)*2Q7-NWx4#}qXfw=%b}iWR4N@~}EO`p@FoZwVF-}j2 z4(9A^zk2s(8X#Ljmv35})w(x&QD|FUXxrYhJ-1KWv}qmR0$NE$XxqvxG^5w!-Pz=h zJvA@>mjCu&x>YL8O^K~n$xby*XML(;cKai(!1&I0zGJRneudcd=HikL?H?`Gbe85X z>7b)bIGv|BUakxkJ;S^Qu#o?#x`0Xf5O06BZ=TL0=XRTp8`)E*4%^=g>)NZfM;Z|5 zL(&Iqh($l#zG29T^?nEJ^hn_P-0iacN77^B1rqP_JA1^cDFXGS968uLB42w@%(yyH*IGcPqmzo)aYbRiisV9xip0x0+f!D z4flW6@gJ}@k(S!j@6?S%OnT#+zbyDv$dJLK)xHIidG?_hJQuW_kojuOR~UZMcr}Ws zF(uUc5IBm*&HeJ59kp&LJ>vTi%))%}J#jC^0%e-=L^Au6xoX8ottr3)BMbf2mD^TJ=!Cavf75v*L3 z)+K7v`l8)lVIi6?|2)$c9%2oq+uM4z)C0|S zw*z<7=F$l`t4mY0t!-_kH8rU#F0H96Rhx{WcBs*$U(@FQ zJNMqV%s{Mu{r=1MFAvVVbI&<njf;n9$hBS+M=^HbkK1&*9XbTnhIVY_ngjBnadN=A;7@h=UhOidDzoqzGvbr;iTti;Ta z$oKK{pbz{>&ouXO6!D77G*ut*8e-f`bBo~d`B1)hD-FTpUB7kV{kCu#-==Nn36%0h zKc$Y`HfE2$fR+jo!67rj=nq{buMeWlhgc<_T2PrbWA$AK&4%y9FsZ50g6j2C78gQk zRTTXV8;G}dQu+F)=l`k*a}jLS?>AgWv`g9iU4lzji8P)MpD-gVG9UF>XpMv@G~u@o!IjQ$2g&68j5So^#A?YM(i1F`>;y6M z3k>433f{p&$thzON_ZIT7(vrQ2`!U|b>R)k-J}|O^r9U+m-0g>SMYTbZUZ}^g%zZB z0|m)o9XysaL4#HBa=xgTeR^`}JXCyGE}pUGNjO>n)~ZwN6!}D35V`Jlh%AeqihD=V zA~Z*j6^J%?!)Y^Er!D1^2JO*bJR?F!%ks~`!L(+>#q|p*90z}gP;4XbDU=w-oJ?@hqFdnYP`Wzp+|LYkwifWTck%=BR^SYjPSZ!{jj z0*=h9_i>c;De|XiG^#{p3-87@8n+0ZTn`};9_AA;k8%RGe)1wt7vm)((aKL#< zqOLY?q4gCI44aY*rtxkj^@k3>{u=UTHBPxSV`pwVD!vX%FX)JLt1Yc!BLlb(g9`?2 zN)T6apkc(uIbKxTv>oclbXZ^!7)R?!aJOAD<4fh7)Uh$gYU{^A&f~G((yh!gjj8;Wy*@}kn!UO}Yrd)bd=E3Nos5tMYBrMo* zRk7HFtc1_l5Lwh5y+?2*F?4KmbiYY{al!soFO3|rBl#D+_D31OeD3R}nz(=uKybk& zU8f*d4+{<>ubP9!8$_c)Jya`F31>`{&~#Ldp|p_`(N0|Y#Rv|{PQCQ=k;CY<7dO3A z4OKaH-DG66lcUw|=;f=5qZBKi*|AcIJeS-odVVUTlFrn!DlEQ#$V5;cz;lQj|{6uYdeAEKB)>(c@ z!H=G-ySpr!BV3bp%P;r;g{GUaKJjDJQgG9jy$SP%fs>n~zx+8TpnkcVNFinM>Sxq~ zSkIpzJSv1m!eYq-MR#T+&P(9T#K+iMJ{95m0uApn+PU~KnlhNC^@ozJZ;qS-tn{(| z@UeGKMaF!LEoA-ZukU`ocxjzkk2W%wIq@ClrtM`|N3ZgR%_ogMd_be*79hEWi zVB|y~6mW7lMin199D|4G6i=PQFks>1wyRS@7a%Xyj?62ffk$X*5ra%<8SgTMvWw`x z=ukru!^X0Zt%$6JDxx>MsYmI0j>1(LrNul5`^le?S?KD-qp`-6+n9JHXbVlVd*X;-&##hjg1f`+W{v}^1*SrLoWJsrlCId&rz*BL6f#@k% zW2>AhIE1MGu4o)Ln}jXs&!sHue@V zoe=D+pRwj=z;M%y^hVceE_Ali&xFp6!#GdLXsl}4+$`C~G_PUakLj**!rLNTX`J1=nhT@SPsGGL+Tz&< z;~SEXZ)oPOMd|1WY>OKn)@9R?e7*q*@weYi{#DzfH=!W%RK-S>XeL@cc*MP#eL#2w zx99Ji^g2Sd?pU&8-!YlIX(rD{7E$NrAa@zfTXS?P>4AJCzeW~dgCCFYQRFCHx-qG8 zs)*>36ka>G;}>@Ltrc8|?#{p!6?GI%PMH5I{ql-{ysrX{XEugst1-Vm_h(eQ;4lfi z1ZlkmYp48`O%#PL0w%fe+dQkcha?q>=zI%wZuJt=8kwQ!0<@#R{8a{UQo&(e? zp0op#HnaahN2roV77`JQsgvGCzzUuVoq}%TOzgqUqbog?buC!f{Rd9;09lv6{`Ho1 zR@%Li6us_Oyr4b3^ogmMzFGfqlqzKM92-k244aQ%qv;ogf9f(06&- zzF`B&p5Gk3jL*A@Y9f>}|K01}y!!Q#6Qa-j4*usI7Cs{S7%;INb={Q*P#rc$Uxl}D zaisFf@=F<#=Q^gKR~N>K z?hilX5k~SDYrWK+HSUyX4o`XF?+4LwX}X)=CxIQ9B8U9CNA6K7;Q4s=fr4L0rabWv z5o{eBWdL)L_oVw1!Z*Y-IVx`#Cf(};W^_!@6#lHOTwCGP?qnOjS zuaTOP2dI6uKAGOs}D!70q>Cy=kEl*e*5}A;vht6^1Bn)?w7Zj8T6R1StsHLkQm`W|ne|_i z%(`3-e|kIFM!gBV+t@)$~s-%kjk0r z!i8#w+v8H_q-R=#3sjHW3*4D5+()fM>q+e8^_zH^*34-egt@)T)xHw$ha5qAv0z^? z;`U}*HUF#2GBd2pV6LdGC3ndFhPyVU-U`sn*L`Uw}?zVp#dsrLZL9B_5IeO}Eq z+^gzp3wJvFZjHjt9p0d3*233w^6k2N71&Q4cAlM?*; zwo74CZHE}yZ|h62FWh&8RQTd`IRdJ~=?u7nL3NH_jdZ!&yVQ1{H{@`8gZaWi3Y5uz zJ93EXaRftZk1OEraBIFZwm@}wJid1Ij5(e#Sy-Tkx)2JdYU!Qh=_Rv2;A3^PT8^7X%S-gYea#(io|Y=&>hrt2Zf_@-&qo%` zu;vBRr=>!lf*%REL&OjH#)xotwEXZ{AU^$n7>YN?!trJ`BFsFUBSwBE_fl5fo;n>s z9*{Y$|Mt}50AzMh!3qYDFZ_NoQ*o#AyXrEW_QLb)b(>RvclGAfEl&yg)`xFT9RSz| zg*ojfx2Lu~aeL|@_+&@3mj;^p)7w+iq#O^I-`S?+KMKbO&)=T<5rF*Z^n}Nj!=NjG zbYJFmE(vy$5FQafw_d`G%0cM2!R7Y_LXh)#;@oTZb;Oy;|JHQe-wYsk$^6Y+oR$NS z+3yIocO~eN=)YfN$dkFFJl?ES#G7w6nKxB$PQ432_Q*eaKY6ZUezAUY>T3Xve|h8P z)P4Y&n_Yn*Di$>pIf~4UzK&4D5pb-_P08K^xGVT6faZ?<8Jr*lkQtK| zlPUA0u}PEJa=*hM)I|-KU6->1~<0Db>GVQ|iD%(MFo}J1upItJ@do6K#7CO%SR^pW5y4_Q@JONNq%W zI1oTZ<>^aLorb#9$OSImf^|8(PLC^~23!yfx_exnKHv*sgIX-IGjQWrzs=$JpNvyq z%=|=&fWgi`dw9Wx5Y_pIB^I=QX8GTUPj#ObT|MWao46z#a`i6p^)z+`>KxrJrZ$RA zWSVSab(%ORtInd1jt15}P+5n!qwJ)k*Gjxzb;hSm60kZ^~iv7UnNH>Ka- zu?PC#X?T|~_Q}4yP8t>|MWwCE~#A7Qd8Gd*-%$j+fv!kP~VWR9;#3OuhqiT zUm@S>mo~N3SG54GZ@3^|tq-H$*`apx`sH8q!K^B_{bZjSbcN8l>vcI72Hlst)Hd`}SE?NKI0EQHqhfP-(SPi8aq5=_jYd}FTUjl< zYq_wpp+3$l-U&wETXo2IO5YG~?&0OGfbUD)H^+>oRD^F0m5ntQR<=}?)zns2lO?>Djc6fXd2si~cPH@oY;J#HZFR!eusH|uyudJ!9sWXyJcpD!{8ltLg zu09{)?M55d?s8*L&^eCtdaMLO)hVSL<&Pz0br-bQ%NAEQ>S-tZ(o;`w@+edAM$m^r zE%zLwUALD8SV0gDm1SrWEzY;5vc~h^Ta(_DVz?XMkgDMzjYIVMOvQlrpGS*Fj@k`g z0?I0=u(jCh>uafOWBBRTvj-2n-w{B?h(`D;y3CzV_@R%EX}<9?H^wVbz@R9~2QlBp-Zef9U+Gr3W_!aw{!EYQ9x9R91>&Zg|-daJk}3z`x?}G?Q=D zWpx#`m4;8E-cm)^21);~YjV2DH+wy=o5y&^N5iP1{&oM*7;#N5YpAbV+(H>LU#;_b z7Y4h~_tvvXz}0~+nztR4j)1Ql^%%{}Pz@PUojzC4dlrg!#EZiBl6#-%%#t7A>u+s^l%HC~a-w4k+-y)4!voZx!`R7uBK-Kq1IiNlEe9-blEwr#e%- z2t7vnwzRIIvSC?6O%ptmBZ4+phYzE)Hq5Ab`J4(0wN_(du1Pcuvc!j6-memjW2JBP z&552lI#64vE>!dXx-S$jdT2?4@}yCZw8$8aRcFKzRH^DhM9}zsmAG&(Oh3m<-x?~* zD(dTMFKAg(Ur|$4L+!VwP_D9WM_?sN1pK4Pp`bJL(DP4Jq{$5Dz{VC~HiSGvfS|5?|3O<65!$%>XQ z@i2^{hbc=m?Ux(1Hdpai@cEQ+eUpX2lx0MtjTu~DxU)-*_fl(-(9`5CxsT5|kLUTR z(la4XPRRB%MKC`n{%V#+;|({lq|vr<+l8wKO)Bm7|r@ zirYB#7fK*jGD$;msYTM1RjASuU4{5;@|C?mV_e^Oo*c)!FUy%<#mm{!x3PJ0 z9M6on9U^D`GbPWFzSWjBHq|31)*?-4qK=P)eON}p1Q{))1;g#MHqsIH=vmMuRzto{ zx}7O~TZHQ4Jl>(qSB({;uj3LN`WjDR*t^n8ox+Z=x1FL_Y*^+q53X)GU2f1djL#Lg zx*O1`2%^PrLT6KB@i!mcySkTpX(4YM2|gZobyJX-aXoFwXm>|K&ynr_()LaS9yHCfMFRJAG_)Naxt7W zN$}B@;M)VPGHO>bya#~7G z3;^$8y1KdI5_x3ieL~8AhmhdCI58+0&pO{$3H_m9kMWwz^PUprU&&hz=_`Fqhb&Cv@ zz$2pyK>Xti1u^8qTP~@8dni8dlL&8Xbp4N&;H`tu|5zsM^ZH!)KQ4a`WDq`{H^vj- zb*2oTUT7&`@;M7{*r&Q-JT{vPemC##YBu8E*pk7}4kPvB>6&RC5ZN2nNOAA^Fj0FR zM>H800LL0P)_?ete#FG$=t48!>GELmCr*S~1!r3)qDWOJsyMDGME9~VW&!%xIdS|7 znjIirV5&+uV~OJvi_2g}LGbBfE$M#1N81__62YqT#Y%B4HaMV?6_)Xv36O!K`km(W zosE3PLWpD?l3&ToOSZvBr^V6UWws1n4#0^2*eWd7;D-WZ*(^`zLwB^3AQ&%X)A{3P z!F-)B_0Lx>K$K4b-bVP~2UqX;VOQ8y>+<4Urp_O$dvc;k=X3v2%MsOhsHqn28$AVs z%UB5vQvc)Dais=-+&Zqz;3M#jR8;gv5-hez@TvZ2pge~rUgjL)GFA@g4vyC{aW<>K zOH2_9bK-9$2#e8hi_IrAyBaT1HHJ^ErtvHWy4d4ub9kV-aSCy@&aa}0Cc~n{Y7G9k zrTAQ(KYkT^p3Wav{j65!kKcqX(fQ+7(RDh1+~i~R;PW}@abu75Fv+J?d`J7(_WOK| zk4i3~>H&wb2Fb5L$_IRXoM!%MlzhtFX1azqN&fiSVQi`7kFOo<=KQC2nDui z{6i?PRpTE*fewv-2nDu*Z>ryE9Nra#ndHn{@bZZ97=>2wXD%7W)l+Bc;UP496t zjfqcaGT;EJa6Io2oH@@XsmDofF7}b&91EisP3Rzx3snvYeamVgPK+T z%t^N{pMyvc6k=15s}qBAx$BPVUKIlbV=o--a})NkOOCP@X2?9+o? z>TFF*HSd1Gw%0k%IZnAT*5ecV4r#xkgED%;LwU@U_gRa{P>PO?$;kVjW`0#;Dc^Wz zl8NgZnPp83ZfSfwqQ7(qdzHPVR_f|dn0P}--8hof4CFA3)JH=P#S!*|s6WHFOaigg zBHWQ3mU!6&q9#j@@fuDG9~n$K;Bk=rjKPcJ|{px#+i4rH69sc{^R*Xn`hAYeB!~- zDW7N1cw{i?k~h;Dj|?Ur??6+Y(fFJo!lZW&iN_6GlRS;|d~3?HdTUDZ(xB;H&&#HK z9G8a^@-Lq|Aa{Up1q0RR3y97QBAn!8oNjf3;K$7qkX?hoohJS3Jqh9qgU;i<0HPB@ zREhFYpGJp)TL6z#qVuFd<4Lt}HrKg95XyypeNaViYX+HSJQ=8f5>Fa5x5+EL>ZNcz-}fvdn-x{%K^;Z49*N%x!Z0pJ#^kiXM}KLngDm3x3q z_%7h3lK!y?PYxrT!hM!z!d2h{lD@!%7XsfV>9r=j75I>(hfMelz+;lW)r1cMPdi8K z5r4~s?+0#?^pqY0p9$P9=@t`S4css3D@=F@_<*DjnDA}Dw@Lb-34aZEnni?X*o4P` zTO^%D3_K0EUD9Wo@CCsAl3r-SYk?0)`f?NA1w1Ch6EfjzfDcLfMiagj_%`XzHWNMw zJSOQoP55iTha`R2gvWrVWr+ChH{r>>h@+&ZnQ#?&Ow#9?@GRhIA|J9+6J85^K+3N$ z;U3`IBz=_$9{@fi>35p&hk)B9{c#gM2z=m1gdd&m=S}!q!2L2jQ4_u&xLwjy`V4#~ z@NJSl*M#Q*ACmM66W#jGDB7CA-2(2$ zJ(bHV$ps&5>yy~JE3nvz$Ww^O4Kv-?KNo) zRjwHCT#6n(p)TYfEI_s`T1YgW1OVs-|US4VB`2OAx1BR%kJ) zYg$n*jswV5z7=Jd1jaEt(gjDb$ChwSQ!;cqrbi$)R?bZG`1taI zZbx@ZFmyWh7(3vaZg%>@w8EvENgTTtIT7v`@=T4>E7+vQ3ymfSrG6Pt6FbMGgE=O= z7zv@t6e6)S{7jR~DieMQ@YWuzg`mvxEKD1JoiqW8-LSp@Ejj7=pXLbYS)lS4|0(nP z2+Fx8m%e=1Y2) z-$8h~tCvrqrQ-k}H%odKX3`yGj|EY~mkiqMF7ySI6s}3Rh2KDC~6m zR^+5xR#dpLhXPu2ySOsGR6hHw%DX(2hF{I?|1Gl3?qC=bh2`_-tJblBCM{ow{vMze za0B28zy|YW2~%Xvbm%KeoUd ze10a8@`Y;BDnGZ1jjWx#a}em}v&#$AHk?t>n@6UXNqk`VtGwSO5+5@zYBkIP7BDT_k0ZVCZ`+sv&!J)c@{wghHCd zavsc!8*8c^!LG)>ZjP6G(4B>MB&XwWPsc&${vxkcVQ+jH&d!q?bvQ_m%9}ew`H;)h z;uZvoxMl{II^<*?<6af=s^uj*=EE*arGqysk(~7vFIdC@sC9%$`$LT~m7Da$4GA zW|?jzF9}qGWzH+`mycn%$D}h6J-(j{_?Y=*Ghw`Nk-uM;PIe}q2@OnW;Qy`$wEpfH znbzD~v_QcwH@^Y~H%??)5wvj|^NZTh-mbh@U5NG{GFn~P?O17&N4HUQ)l|`YRO4)E zcJy|qrIBBDu_)ZZ7h-jKxyOx_=E@%Yw1A`2h4@%H!lTQelaFC>Cywr*lM)R1{C+fj zXzg(l4pSWME{z{Z*SZI2Si;?WB88v0p`LWnry-%7B)?xfN4x9RIXOOqMc9I(`1~jE``IqJ0fEez+8;D=!N&Xa7_R|D;f+3 z(eMIAc;!7Xu%)7%lu<;-E1jIQj_+irkNR+$Yjh`#+Z)!^KyjowqzKm2`O$!g(Pz`c z!+T+vWWu#J=oj)Zo8oVX1zg>9-;JIePy~hAh%i`Ee$dN2{h|jCW>D z^Yqr#hihv@KX4+U(Odn$ozRrNbl()eH-qlE!TP@{#@<@LSTD;JIKXPo59rudcsh-| zV%m8RG3*mSKQ`d%ai8f62XVrS)|{N`BEI1XW9Kuqg8}7ypOoO(&qOCrHB)HLER~5J z2bix`#*A!cN5y{KU2+(QNilUQVFRX?A0J1lv%9`9{s6 zwu@8uR59VG3vI&}6UwG)C7q!S;&=Y=xe+;cQWg&RY8^Nm(N*t7u*UFJWI5as6c_)c z`cyy0Q33Oyluu&uO-3~INgOK~_GsBc%r|44S6x|#1MhWB=`@e6O@EQj$Ud)3JFq{G z@&f8rM-LADi}O(YFe0@vx~}4ElQb$%8tLKKoobSbbB3QJHeC%wN{~+(hM;s}mQKNF z;lhbes)7wKF=(xzmQ%$xi9m?xnB(X3_Smw6MF4n4bN;+BMK!`#JmmP=g#`o*$@!81 zYF=_z-d&@6H<$EqHZ6d}`>J~Z?_C}=d}u1LiJn><%`QKmHm)skB#O@fAQ!ZUL!?hA zdnL}myF9oLOwOlyLqMd#G<=R9AIULxi?m}Jra>+c@|ebIEiElt+fv~UlBe44IafIX z-sZq51e3Tn*5YGFIquTOiqg!-hURhH*B$Gb$8%z@ag~o|T=)%Pa^R~cHu8m8UdtyF z?+Fb|XkbDE6B?M%z=Q@SG%%ro2@OnWU_t{E8ko?)ga#%wFrk474NPcYLIV>Tn9#t4 z1|~Exp@9hvOlV+20}~pU(7=QSCNwaife8&vXkbDE6B?M%z=Q@SG%%ro2@OnWU_t{E z8ko?)ga#%wFrk474NPcYLIV>Tn9#t42LAunfcea2;(1IuU8|kW>~y*{lsTL|4!>cf zbIVQ-uB6b;al+glGM(!rgHy7DT`rHt3i#<7EMW_{H)!C_c9$?jaHQ6cqu=SyKo6ya z=W@dzc}A8y0J-jN82~44knlQ%0rwI)B9h+i4{)s06V@GZdfE{eAv+X_73 z2*%05+~L*~2z|ip_e*;M$Jb`%*;tryZG`R)Qd7s<5uq+kuDhc>>~`qEX55TJ*e1Ti z8#K&4&TbrC6T+Zzn2D>!5@*XD(k#!O2YciBx>@qI``-9HanetLo9>Cz z?~n7@a%DiegTS>*;E*OwFXJvcfFH05uokch@BrWmz;?h8;4MHDuosa03hp2V%mmB@ zWC2P6)qo~IE5HNj1*`>Z0&E3r0}KM52fPJ{0`>xuU%eys7=Q|}015%sfaQQLKrdh| zU?X4)U>o2`z)rwxfDZtB0qiw|3!nllfKq@R&h6Z{O8Oaw13?#t5?CAOva#jMFT|B&_$AGjlw?BB z%b{qYXXw4Wo`0dXw6xKrf5@5Whmc=vq*t8$Vne>AJRm-qhC}PVKp(=JK%h8)R1xm> z3z1q}RTLC*i9{(|6XaLj(UEw&_?!&s0gjBr5M<#p|0)I78JCSCk{rHIQAU}m+UJvL zG?pmk_!S~tSaT1S%|t0z(bma*ZR+#mTMpw9$-cJEP8^$GbQYCiBhT=>Vdk0g6VD%C#I~O*IZ%#{M+jACI*HnY6BQoA5 z*+$$rMY7Au5j^A$Ph;T~zhVP?UP?5LkPO{blORF`fNS;;ZgA~)T0JZH`DdHvoG`a5YOp>J)o>Kp&kMCvYy#JvK}@nT2U{H zN><=O{X=w5{0{BMvd#RC+CdY(lkf{Rr=~%_yG-~8z||E(J_bAnySTEK?Kk1<_h@UD zZ%$1HJ;j6{1H2V<`qu1B6FwXGmYe9#-ZW-0;ibUUn?Z*MwI+Ny@P4>Mckj2F@Gjs3 z2s48ReiOb5I767>En92CHv%8NP|&xS@P~lMB>f2!z8$!Fk&quU;co%ANP5(S?*%?2 z>Fjj_p9;L6+=p^4z*XQX!d42LbWY)1F6o5(fioGNwZKayzJ+ko4^!AS6TThz@J6DS zvRx)T27HL*OOY{8WX+=_`q#MXY3&p zz8yFlxIMKW^dS>I47?R|1fRuBIQs+SdAw9M)r6k_d>hHLem2{LXAzEXF_gmnYLe%9 ziLn*HTM1X8Ln#9hb^~x#$~*wPRN~u7Uh4KOqD$TGB|68G*fDQFw_I1rtQ2^u)WZtk ztz1`WtRHwk$5plk_!ddu4t!A3KL9?=@lwX{-2}VTTN-dJ56mQ7#%qDZDg1@NwLDWT zaiTW?@2Bu1UwMGrWu9L}bS?h_-y(6cqdiN#;x-&-WpBI7Pak2xZLYY7iukq0eYa%^ zX8Nq4nP1rp36p$fFC?hm#kFGeeF8IH=7neOP*WFbD(WKfXUn^Yb1%gL)Q}5+@`nr8<+y;pB<8P@o_>)=e<*bi?0J2 z_G|e^kOfcw1d?<^d_Yj}^v@khp7@-R##@HVkqtM6X)VG%u#(3iX(Qntfo}o6P0}9% zJ|yucfX5`h6L^}Ge+{@r;vWFFOFRbLFLCxqxGC|ez+)1h34BPCp#aJUvDUxu$Z`2C zM}dpZJjbQFIF29cq;cF(l`soc30$gbJN!=T=5bCQ0&bD`TfprSj{^5gd@t|;iL>_* zABm>{ACmY?;4w``iwxe0-&2h@2l+i_K~Ti7%z=+9WRgYr^MPmvK3MAv28Hw>%rexLOirirlE|4dJ3n;U4-_ z`d2oIN97MrayhLpyb(HKZCuYOTY;xZ{Bhu=?SeiC+%NIxfyX4i3;2Lj$b0~NP~tI? zk@x}NewUC*c^~N_@e_c@BtDzy9YV$ed{E-0z=tGm2R?*h&`h237@7U}OA z;9F!|D9+l`5c0_6z-4lz6fi)eWZ@>@q$UmLYBD6z;(TDlZ6k0kZd-tBem?|!JijRn zXyWAeapJ#=L#(+dlXxf1MP9}`c`kH^cgg_b0(&Wga2IC&X!kd2<_8WjZrAwQJ8^!{ zJaZ+ARV|Bu^#V%t`0r zKd-W(uCf+$UJxgOEVG#P#AGSv!Wz8}f3VBP=L{%wWm;?Txg2pdonhuz)A90|X*Kf% zo!m8rSBnyb4p#xj1s`%%G=WOj&eEp?0zwOj1TMGiT}VH;%aUKD%he+qea^&v!|;c~ zME=RU$wi$Zzr#Uy-_uj(_tW*n{F}X$(%Ogk67e}6CnK8n*5si1F0!w^$z9D~qOZD) zmuo`VNf>{JS4Oaz6o3_+A7M z|8)TIPk>TLKhWbKndvzNIO&(3Z<+87z)5%X+-SmYGT{Rz9AVStZ!zJwns7=_N*AhE zOw|cY|GiIQyN*7rr~zpvlM)F}9E}qAD?XMZjeZANsZKDrCYZmXvlC(y8ko?)ga-bf z(7^DqqK>1tqL_k24Q9ea?Tc9?fz8_=l9_lWG%%ro2@U*D&_M6L_&%N%<|jcX(9HA} zV;8CcdF426RoIYYu~?_2E>!t9P5G1zE&Z$XaT%IFaXH24n&k3Gju~RIfryl ze&+`tId(uCerM#8Y}kv%d3>Q0A7|(edqVge11(!1PVFnjSN{yRdAv;We5Qqe8PJg7 z@iD%Gjn8!A`$w3j!oD+nS_m7dT)M0^>yTk>@gY?1&^ef?#7pHLXTVr3$70PUJAV2Q z20m*TqHl2O(?*!Y_i)E6>X($&)ES}2Cjb{4Cafpb);Bhq2xJN0yAHYWB`5KrLow!x zBA?LsDQ*89sWg@=#G5is7v7XpZor$yXiwlxW6vnwN8>&9uXm)-WTY)7o+@1&NtcIcthN3kI$1kPamm3$#|y#(Ta2-bTPyz!QLH0rVVz+bVDipcGICxDen090j1~#p9FN^?*A84*@O( z^a4Hr>;)v9kj#z)3<95vcQc>^a5-QAuodtm;1$4ofc=1!6O-97fI{H2@U8+Z2Xp}Z zfXe}E05<~e0Xz=a4j2MN0eb;SCnd8J0P_H)fI7g106*X=z%76W0Q1Nm-tPhG5XK7u zGfz%t>3~u|9bg5(3%DGx7Vs^=J%C35gMb$S?*Kjq{3jrFCj17>1>^y$0ZjlWAOyG; zuo>_W;KzU!$H9NRKLzXuBqN>E04g9IPztC8TmtX{t_2JL_QKw7z>9!K0e1p!0E7Ug zh(|hL7T_2_GGIUAn1b*NU0eS$}0yYBf13U`&CEzu{i}05rpG^mx1egWL z0h|lC1keMx5pXA98(tKH)O2zY~ib{$4CH?~k!a#&9f>``l<`G4N-8FdA9%-O$d%wbo*9iiogRx+mySkOy)YVi z;l0?STGv70&(g)Wi;}wQ$asz zG%|GJXr%YJ(Magz(a46DSmd>v_eTyOZr$*2%I;WX*;a%Nu<#F~k$37oiwuRoh@A0i zEOK$xXOVV*8}P);Smg7UVv!Yhk46rEITl&;yI8~t+J;@R$n+zikJn<6H=xr?j)v}^ zhkfYH3Z0(yJah{8EOYlq(h$yTtG}zqYFRXLAz=3$*n4|V zr28d=17W?fdr#z7pv`XI6M6WAJ&|`Hf8_U~k#z{$dspp=JOmm4(mjz!ozck3TceRM zWTSq(@B1*a@C^73*=-0v<#CUKvnV~T@_;*p18AN`%+g@T55|kE(u*yuW9W}54cJca z2)dSpF>it~W70B*JA`jJHe$ZY;}XYg*o8`s7Y;f+*dmtbL>2bOcwC_|^y68%#}{d!FEf)uE%u7>uVLQI-I1Hg*fX>aT^d2sL-er%8P*1zLkVe4 zvPX~y98MVyaVUgcj1)(Hp(#C=v&P$w@1!EEjO`d3@){@0lJ07n{;f$$1OY;9zLer0 zREHk-CRi%7LJrzFCS@6$tL0qS457lo?uevi$Zsy`lFU-fZ`R>5#y&N)AmR&} z*DS+#(EH*kkh#gcbDp_)s>|ZRlC_jWs!<&hWXpN|AodkEb6ne(+zXdcM@n5cavraK z*iw#*eaiPJ4Gwn@wS_zjfVz}(wO!Anl#5N}u1uzx*mk@~Gx2@LmC_`=WL45Ee65xy zt!As0W-Yw)d3w0pSIW(UKUc6*cc~XglAY`c%3oL~(DYCuONVA&ENf}XZe{Ehp59(e zhx@!#u0>V}vb%Ze;$zr2Hd^cULh@+JJG`FDPFm)6y2`s8fhHfXd=~YETx_kP<4rzI zfGrZ~PoA-jlqKRr;){GZ;DNFq)Hu(<9j~*_7jk#>Ng0&B#}V32>~nPm zgq*?o4VO2}55p&C1zK5OCp$pfZ;Wfz7jSh3XjiNZ^lz{$RWp>a?<#gwj)r?z(%x4gNo5(aX^@%|L@J=) z*Wd05X6yS4DL>zRlw6lr&0b*hX_qc$tfS{b`e*Eh8Y z$XRZ0HMSGOpJmW}g{N~-I27`E`JqcXUVH-S0HGy`KT^t~GZP=@OhZczR9)>M&7e51 z#92z=In}X^UA~BB`iM(-I%`5MZ0bAQ2xXJ2Hw1rT@1$A`A%{!_EYGPzC$jYNMq$#mZJU6Qgv(rDXMf^)E@w9&XJ7>s``2}qvH3~Z zPVWtRu)ZgW4D~c}0533x+&q(9MGd-OW$o>>kNzx!&MB^d1KX*Q=O}Kvbu02NvI{S- zT7KrWB{zur=L^z(V|&2o@iaKRbVTKIgMtEyU7zG8ye{{7d;zw!v9dwC2?8mJu1LsL z<@1JWyiS~YYKQior+AB=9CW7jsAcDyJ;=4f7edRaMav-H7NHd&$I)}*CiGxj>{*hm zb0N`!=rj_$fw+`QrS#&88X1(w80sb7ML*Bg2b(ZaXBh@6)k9?FJyG6k;4Se_xD6hB zDfK2%;DI4iR?%E$*Sw$s$;30R20lIezFcD5Qi6FFyz}sLyn?=z1#=rV%qtaPjt;HO z&_adT0sTnnR=fvyGB)sI#`^z``3#r`04!Z$D!>l#1Ga(Ix)D6W0YiYa1*oOyeU8E` z06Sm{WcmSq-~)hdfI*mBZ&ujwEs(W97EpRC=%};sbBJFp0%r zzZ8BCkvkPhY#VqMm}7YNgFl2fJ!u)B17d(QE8M`Fo^A3yB;R(t{Sqg>1+w(`<=Zab zL>rL!HWQ8L^bAQ_EP*$G4bJ9g+kh}x66j_b!nbS}l!4#GbL`I`j5`pI-=YS17HRYx z&UHMGup!OpDSZL_7r}c;nA3*9dl~YtV6*hAf=0hXui@wSNH2i->3;)pf7476c?U`I8<$ZBj^hhUCt{ZAl_M6WIUU*e&gO#k&>nAwa1Q zZ$I)Uzz-NY2Kf?&Y5;WgIOOjWAb%3fu*yEteR4P0sutb|>&X$o~NCiJd9%!BY*I#`Dh`OH1oPc?=o!(z6U)v-fr0!!q3l zWV#KR(k;=QAsJSp$8L}34s8PsV0jBVeH;3D7isVpBW!7-(CHsh#`i*h06)w_fB}Sm z0D8Ba2J`$R)-S^`_;<(wh8E-9Vz@)T_Fn_}{&?B|{2G@2Sw0iILA?Ed)+Uqe=O#Qp z3UMaqWs_JJz)u@+@C=`UvWdDj_5{k(F94Z>{{Q^%d&tj#()Xcfz%U@~Bj^OM4KVb9 z&__SsTSlPQKS2&K44}vVA?j}c1J6!;$hAg61E`>v?uI;I5THGSd*BE77?3eL{N4hv zgZ97UvBQrofEfAp3E~7$KSdb>3<1zAhAH0DJ4$VK5y+hHr!L4mKLT_lJ89==WX^eL(-sWcu(QigR*$6%3 z&2B;70N#rJ0UN*@z(7|4+kg+ljTpIiQ4+HNh5&2@!j8VgP+1cDF8V$X03HK81$Ykd z9$*x(AHYhn?gls(a2jACARDj<&!+1WOg**G(bL}63_@}2lN200;~gU2HXpH81NKe6n=dJ_Ww|k z%#_7gZw2jjz&^lkz?AZ2b|TfhxmI6G0 zKLHxf$GRP4UIYvS{sNd>oy<-KJON%7-sOPb18>B;8?XwH(U8n;!h0*g3H+ycKL>ah z@Ht>cO)@(LFb|LmSPW;QK43p! z>XKx3DqsPi2(T2;0SE)G1#AG^0oVrkIpA5q+kj62$#u!>cz^}47|;sn1>6950Pqyx zO~5E%GScP*z0X_p<^rk#O96d=>i{fh>16~2V4fq)F1t7T*x&h1t zWC9iengLyaAmD1iO@Mm=7R*#tuqA9cyM*O2D{I22x|kKR9F~u>km>AnoL8R4&cr#< z!*M3@2sR!2xX)%Mv6I;`?0B5MJc=F7j>V}>6(=eeu*q2AsKz*L1uJFeu`*_3xeRea zKXwvMRZhVi=a1O~>^S8MHd*;0yAvlW-(gR%?_)0N9<~K@op<33=q>Cz_8Mk3E@QvL z%$EnJL%qz0Goc~2fnCL}#_7l_FzSAlUCv%+J*# zOO*L+u2QUIC=1yF#inGidCGjHRXJO6C~Zo+@;l`P4=7udyOo=j0p%v; z7Uf3e2g>)9jmm?{LrSKyP_ZcIDOrkDsaEQgY^6b2qSPpFvfb&J z{G=jfX3|;gw4^CX=OmRV2bG0M=P*lBMpAZ?H7P48GbuOefHFI2a#DKINl7Osotkt? zlA2Vn%vS!PEJ(^^g-UfL#i5?hSI7ca6TI+ad|sHWgy@Sh#71y z!p(AW9yaJ^9eU9+fP4#Qi6UK+^WR|OrZ~(7@}vGR@znwO7E}K)*m12wSt#Ua?`p)9 zyvs?h5{(BVGQY@`9G&zFYCsFb!OyK3xMBw43;80FA(M6dx6HE~%FUd|~q(xEd@R=Rr<=W(qEZYebQ$bF& zY+|8|C)m@<`M6}1b!(Q{hFOn#nKxNA6>_sR6ImLdup{8af-fxD_KVv#0FQKN;!jX zv_kQbUT`DjGGWF_-Pj#oJTM(t-;BA9Q8FIRla(r93v(v?QqX;=p>~LVG zQIOwH#NDD3d}W>R3;95Z=@hP7L35*9Kp|^(1l+tI!|h_7Z|vSQ&Iw|jPF#vjp@E6i zEu4aZD-n0q$0d|Bb3qeMSyvimg)U80?TB0>35MFu1YR|p2%-u$5xCM!X5M8nnazPE zFL()+d^3-5EwG|0(HlOJ7Nod_!60KzyW0`MN*j-=5EV39dmTeG65Hg2x#cj(6yig8 zN-Wa0-rmI9()A*OL(-@Lc${EMRa-HeTNupI3 zwp(7#_wL|+G>Sioi&t)Pd5bPT7(%?9bm@k@(a|T<@NH=I7sY4RS zGOBbFIqdh-KDlv%YO1Uad85AOGkU^}=PR&Iq=5x#(`ZxaiYHJ}Di><#*DZamwhJ zvS=n4wYlxp5$(jFmP38~3z zWPZWAiA?vzTX;C)T$B`S;BY%>zocD$oPgqikjh-a%Z4OTSjQpLfWl1X;eiX3W@zOQ zLb9)gjsmR_OrIc-A{FK@5D*RDqQI_0ssw0PBDW|=jeXs1D9_w8das1;q*BV@`!NyE{KK)@of(c=b!4(3=xM%*pOF%l5fP5Uq>-eJy_ziB%BdM zZqVuYSPHj^x2DfTvsq;jOEA*@(0H5twhQ<{RN8S6g`Z#&-8@$SMq4ex&bk7ZR|K8f$1*aSLO z(-@ZDA!6{c0>@ev){iYGadeuiUI6CsICQduEdXQW4^PHk9b)1!{8t#!)kDxNhoF}p zf^G-B-+?oJ6dCz+Ik83{<%#sT3+oQ>Q+ozro~0S^UVbUgjG64uz+8PLX6fyK#lY$? z`(Mjy*dnY5)Pgb>c2qzqKw-jRu1uK6KK(*7{rwCW0DnZn&*0!7DA?dadt4446fgV& zrSqMvqocz+Ouro+tD9retX)h9)+OK~vQT7gsWh_I4#q;g-{fY_hn>Z}Yv3H7*2x{-*6GPp zBHTIThK>%n_5qF-VyhXs-!Xl-qjldDmQ4PWneFaqjZHZiQ?ZUgA!3u^e&i-LFBwKQ zy`y9D@QfIna>Rq%Du{q*2F%Q#^s_;@Fr{Kf>|jddrlbeA{d@~}$q4pAc$NC|ZTC+B z?(Zc%&0r7y{Ad3CaIxYhax6BB{d~(5|8~g3d@#j-$CmF;nZgDIAIjbQD6~8U^2Fbl z(tq|Xw0;8jxP9_}$_)P+_~&0t$#T$taNmrBYf2$M-2<@`wvd>uPL3v*_=>;M>j$_6-W;*i=WdHpakZUL8e|k z$Of+-W~BhOZ;-Kb0FwYoFe?cEB$P)*0)u%xAUTpD*-5ylWD2ecnE~ybc5skgeK0oZ zAm#HkfC|v^`5^G|pMJ{Rl(EGNzd(5&r@W4Iv{Cm%6Og-5%+M?9+T>xbB^pEIa3#qVd2 zemlle_Qhg913$&j`~wjL=$rfY#rDI+l(lQ&9HW1%;KgYfm%R2*0@+l5kksHfFbtU( z$wC;r6x1}XC8~+AmnBV@s2-xKWsvRcC&?3$TW2zxf-Qb{s&V?nfwjFd)(OmuwLBH| zO#pwivZrDV(gm(q<)e_2ya%g$NHAX2BhlDotTi6PoKWnw0IH+$*y(2u)+Ljb!(oSb z;yGOM@~~2QDON6VQs7{JoSzkt4*}Y-x=C**!m$YJiShQo2m3DAcXRt?2$cu&KG<@= zmK&HC_R0|pis4vypc5WO)YN=6m2Q-{7PV`<3&KxD`5~9hf$S?d1-24?u9o33+-Zh8 zR&dB2JNyjdFBL1eKE!}-4yMpHp)*X_UjkHy8d#?zWR5Vk# zo2l$kXkj{5n#ltT(%MR!bmT_uHSUzmULkO zzry{uVdrevSuMk1E{US#`K1jZf21OGLFKL=KA2?;JwGr;&zgTnDvP)-z0$rpeGQo= zF5{CjM!wVnuZ5+9%lV~T1>)O{_=-o9>E$w)Nts5ZNf;WTyid>fld|yJ%&_)`-%8Xz zjVL4a=>N3fU5A`p&*grR{AV;3bZ0tMe$<~7Z)%(r0CrmNI*$v>pE_iE1@2M0Qi^rL zH8(GnH86D`B#h1DG8XKWuwaJ-y(<9>d3z4*QVyaRIUwJM&^TZc;a&ol@Kr?c&qsX7 zRS(KB#jXSXMR>_`fno=@4Lmo>c^7^u6bpEX_rku&4VPkPhoXm#@;9c8c$ocgOVn!= zB}!|mxm8{UL<#7WZmOuq-0&&DeeQz{#gFRqWn3Z?bpt)~*kLBWn&Dyqz8QXGB1{=l zHXpl96r_Tv5dwHS5EDZS)o4R9x%4u;t*}Xb)mrY4A9+&&?m~RY7HQHC9a2hkLKD=P z8%tB!e;~veNC_K$7fLJ_G%Y22Cf+t&~o4vTWI{+1F=3lKqS99oZ>4^K&lBxj*NpIqA72x!=kCMee7$2XZgZ+n9H6 z-rIRW+e@~$ZKvd~%l|OHrC@czodu5<{IpHoIrlr&pv0P{Qx#jnk;*6yk_hkGqdpR%Ve3p}xJ2m&axliTh<=vIH%+_oBhV5P3hqiO`m*+o{ z|Koz~1sR2l3RzKdQA*L&qO_u8ib6%#A{+;bZY+MF_=DoV6rWdeVTrq>r{wP?tQ6Z; z5vJLeI!nNEjb**11Zi;(LNk={TE?!7w=#w^nyo$78JR!KY|nZ&t2Ad$?)uzYbAOyW zn7bqQwcK}dKh8ZoZ${qnd8g)`k#}}pXI?n(io85qi*1ALN46cdmu;Wgj?7o{=jDH% zKegaMVW?<*(aS|Mi!+Nmio?aLiwBC2D=8}JgmyNUY%SSWLalzUg7bxzqbzeRR*TJ2 ziI6o}dM#I3zG1n+veB~H@@va;7H7tqjN3E5lW}jx{Tc6Oe3(&dZLqdmziHiI-ETc1 z^OVfY%%aSS%;wBX@U`l1W^Ty5E%VOIyEE_4+@Ef^)a>K3Psu(rdqK7} zyDWQgc71kp_C?u_>~QuK*#p^+Xa6?)rR-0$tvM@l+H!8oc{k@zIZe42cjUjA|5kol!Eptr7R)J_S70ecx2%jh4TwD3NIN{%Zz zwPbe5f)XqA&suRW67s=`mbI1*mU~eqAGbVXdC~Hkey)rwLeR=k} z?3=T{mHj~W!`VO0ek!{xXMN6tIVb1(a|d#VbJe_3&BzmdNo{|@A~|3J;~T>kI!_vHU0Ke-^a;HZL=3T`cUu;7J)KNK8YctYX4!py?_ z!o`I*6y99;?ZT%E-zxlb;nbp@qAQB-DSDvj(W2*zc0qF)#ref0#SO*X#g`ZN7jHp5 z`%-al$(1FymE2$Qa>?r@AC*vL@*3hW1Esgs;<I3F^~1a$ z=RKSEYTh66-p~6uZ!~XT-hsTMY$qZgcI( zq3bc%K=(Me!+nw6kJUG2Zhf2;p){}%t({zC$bv1Z!? z-v+9&KA!|fg(pL{=Z6=EmxQarHQ|QvBjJL`agoZ%X_3{DwUNI>ZjIa#c`WiwUvA<;hm;EjKd-gBv-$DA#jtP!q9Mc`M z9Vy2e$Fq)49D5vnplP?mR(;^y?feot*W~KwI>2RhjdYE71zmHnswvkB*BaM4*PmTa zyLPzVbA9B}+=Jc2+~eJcK|ciD)7`V()$UqQ>Js-2u(=PqpLFkVzwCa?{ek-v(9Y}` z>>2H`d8R-H=6TNX+~hHPbG<{nBfUxQ`Q976FMI#x{Rtdm^Z9%c->JTLeDgrh1%VR- zHwQimED2Txp9sDe`Z;6{`@<=4@I#R=V0R`(Pl_%F*Dj7-73<>M+wo2l{wcDbY+qtO z$KGPU+rHg?q2nRPV~+98Tb;+dZ*o83{;#4ymP#DSnr2mExz{-^f`UAeK+~G_`dMv_z&^h{e}Kh{3-ua{&)Qo1Kz;W zKy%+XSfsY_3nGH76*6&p4HHC z+dc1q`cZF%ccu3)-lx2;d%yHf_RaK_`d0e>>U-Sxo^PZ-m$;r0y!o@MpZfqv z?qTq|qOe`l-E-ZGV80%6zv3Q^Ri7_b{0z^do+mx8dnS6_-s8RJfD3nc-|~Lv&GQZR z9p-cR{Jv?R$~<3{uhzH1cZTmA-#XuezJEbufA9OncZUB;|6Tqs{euGGz>Gj5&=R;3 z8ft669GnoG6)X#`f=}>x&>Wf&niVPwtqN@l-4%Kw^p8+S=*`gkp^rm9!j|<9Tf#%b zqr*06?BZ})_@?kv(9~LF1nkIhk+R52aQe%UUm_N8`od@}WdFtJPtpBjhs0*bs$w_A zo`}5{`;2~8m*kT{_QULR?WfvrvOi(p4Ot)VnCx&kj)kl*0?le2jgIpi7sBRW@L?R z$oehrZSFVSUqXh$kmiKvY|nbndmfW_tM>(O%(u{&^sRw*e8o4=U+%B-{}CMYh5z8d z9Mey9<^TCm!!$a}VrJ;L6uZB87k(}^B;UsL<#_&Orqa#z{<=zb4 z_zHC6w~@io3TVO2(0mhPzSvA~$sMt$WB-V~4^AKxegcrSs2_j+da__B#7PK9;*4cD?TEaxHiN!F{RwGw;{lAH2u-yuOX_h5zRJ z+1J;9uKx!AKmEJ>THrufrHO�v84zg~zu&kQ3}5oQPSR5L^;$39b(wiWOb}N$(1M z9oiH637%b_aBescYiorq9)s1L7@heF*f| z#h&{-Io=80LT{7zG4I>nuy2X)EMFV^`NJW{_xWGMObQWgy#)>(9V`wegQo>A3bqE{ zgQfaBcvxt1C<^KQB{VKPIPzs=5PbRoEYMTYx1-@$e(cy-GWJnyH^*y}^qy#+43B<| zeFH4ZF8BdI+D`$0ed!qNoZ_S;Zgc&^^`a{XKdThuYw=tTF4^E|_1uhcKJOXmJeOc2u%x}5~>N^7up#b5)Oo8;aTAm!kLM-o=M(Xk4ayV;R+mbzDB_4l}ca%-MGo?K6!XR5ynoO>~1 z+4ueQs0-m!PlwmaX#3hgDmXoSDlAc3_`t}>NG!5AvL7>B^4SRcC&J`)CqqUKuslWX7KUW5p4jr}(J4)_(r!L6$tH@n_+?}pEJsOLye z9U`uCy`Or&@b>i$@YN%x{Kof#?-+Q`$N4Lu33mET!4bjQ;JV-&!GWO|;-YUtzVNls zd~M;c!j+M#NHX$KQ85>+^Q_ofaK#<5HfY`#;BUSW`x5b%32%krTt2(Sez^T;yBl%WeCV>X z>}z3%?ndnM7`*H+?7tvRm;kTE=ZIji-P(=m*Cb=Xj^X zxyE_6^I?%W`rH|I<-2AhYP$kH&1MujN>kRiOEk{w0C)5YxRJ$PG>o z9u!^>E(g`FgB{)(wnZYK)6&T0k@q5>Bi}MSIwm?E6j~ImgTHnoDD-ahyXY^`L9r3B z!(&ro^N@w9jjfEWiG53cZ8qV%j`*4lyq7Z(Eq!VC8f5q!$F+{FjvJhHu0JA|@qp_M z*B!8`qv4C*23_Eq6I=eaFzt9yidjN9g(=$-^GCI-LoJ#f(v?yzTx zrvaYvrJg%H_k#N0dJaIIazEcx(D^do+af+6i}?IBNbsK#>pcKTect~s=-5yFKOkm5 z0{(w-;IzP1fpdb}gRdetc3|k>P%?C5=(f<`;Op&&JlP*27e}s-+=Iyb1jM6nM!$_} zttMo;vE#ux+Bp$k*g4LdoG&;-u9sZZo|Ujh-+B7OLSF2>)Vs^;@NI(M`3WMABaoA; zLEhvn*uk8@0Ict#&|gEN!^^@~hHnGcejGj`QUEHJgMPtiXS6V;wc(w1JZGrm1jplu z4#MEDm*MsP6Pd?7-ofx|Ux!D#$2Zu2Z}`D55Zf^>@a;SiJw9#kCw!01&esqd{@`@E z!mcH*`(2N^o_Brj%5xv#o`yWiJojSvIz$ zl6s2gBF_z;2at>V#`6jR^Y<-}}Cg zeM2C#6Z}W|gRml}`B(VQ@Sg(>dL8uVZT=^qHJ^jd{1B`0C9(+n2be`T2>H4I?B7&G zkY#~o$RwN-I3Ib0%L2C{v$Q4f2r>x&gfH}N;Jd(2@aS@b2L%sCUh|mX)ZlT!*}(Ns$XCYCP#hHsI#JHM$e006}>*H#ri>$25D0}u!8|~9%lbDe5iX7qyJ)`?O5PA z85Z?Vj;kEkJD!0}d(-irV<6(3v9N0AIxlek-Pz&%4_0EYv#+bbRqOhL>rbxhk*C<{ z`pWgaYaDp{NOu+1;yL$!;mcUOBfJNBW6&c>Z@u?Q@3r2W5Y6`S6~QVq=Tw92ZVM=R zz5i+Wx?TQn{i6co0*3|)0&46~?pkE& z9zgWA9h_|O4)sok<}dOtfam+R_jCC2gMGt&lYGbcjz@O;O5gRq+u;X41V8e5-^<7> z>=F^2*+1An%s&nm+yhI!0vy!}$$lCdv|peW(fn0`>jN7Dw+G$~41mW{hInOoXq?LH zMniK#RiQd$-0ll~i>yQfncKgGKMU^-=OSC{hutUyg-%1p?F?Ahd*S!L9C-s)_Dk51 zLU=Z7kSo71dTDfPbX)W}=!1`PHW|+uaq&n%oAR`6aY+KS-1vx@(H}IAn-U z^=|M!3fuf&?~%S!kRP}R+2V}PvB`HQIB)Z>c#^v3cq;VT`H0fC!Lt0|H^HVj0;eGV z`)S}nWLjniQ^EU#j|RUA`jHE-2%QCQ@7mC<>vj z969NNNJZpS=%l+NuSVXC9EhCaOO$yqS?%1 zuVR1u4Eti_d{d~>JPB|AU-q{V>6sjLjunVLE^~ODv!RntbDjt4UW2N^2hNY3-#K$! zgIq&hMX1!YxxRp>y}x@1tZo79!x^CF$H*FtgheV zJsDZ4DsO{`126a9;~nA~>6_pS`KCaYD}Bp-fAC%4I}wz67@pg6{+Il(`Mrq7t_?gN z=pP&hN=-%8EQyNDuHd)9{ZNUS5=w=dLT7|Fiu}vN@R0Wp4@1>u3cQ?!$WL8{*yBcc zF?Yj*c^v-B4$!a@UJLW(gCplh-iS^|uI1j?!^pL4gV*~ayxyLzEhUJ!?X|d~@)D;7f=dz6lP3CYv2v z2ePPl^KJ}p0R`R-_l@iySp*G#cI3*)b&=a6A0RR~0UCZ8Y}kF#hoes*^Sl$< z{bTqrxv^2GI2{YUz8HG_)!4sTGl-kDuR)%JL6a16c85bx1(0uE1n)PAJbN>8Q1?0h z<#-<+|6a%b&XLY>PMr{o-hHeP$ z4ILdWfQ0=c{2_Gw=txauIA-oZR^&ADLWvpQdBC#!%w{qS(H0aLw?lr6mr(DdhUal^ri1GM3Z+TavU2tBoIWk@{GW_ z@N+uhmHs7oNAQv07eOcT64l7^tc3R1h)nIvp?8smJRDsAQaBiSF7i*TwGIBtKcd62 zs^@{@e?Z&~9gMjhgt)E|T41mJFys>};H6xFda_Abo8yp|T8kROQLat!V28M8xl28M zKlwtnsE{(UdQZes6Jt*zro{J^ z@XS9s4|bjB+U?rnJ`okA^_cT#o{jJrKlhf0PQ~~xiCr1n7<&e>7Gx3l}R1XzjBUq9qFn-hINbU1ozqQ!(l&m`G z7s&q(@gC|u8kX@g?~~ryz67*&gYSHJU#yzEfC$6skNT&;=UnZ7(Ek`T_gko)?DqE$ z91yU=WB0@BEJh9GCe#)j!4N#IH$ugcZ=!qQ8x+HDJcoSIVHUZv(e{am0PElfY_fl3 zcRN?1etjaS+2%SCTH$>68=i&8CtBh6;fR%>A9>D4uxd}i_jwjx`^%x%LT`lr9eNM; z^OMkLsN;Sc`T-Jd4)2GW`~l%1un?odK2&p0Ko0&?-lQ}`2m5y2ngb$X|H=fR_> z@^*UTpvZQ_@}D7>x7SyUyxfb3c-}$Pb`ZF3exNDvQ9uhWM^5~z;B}C@pMw!dUP-7l zlt4y#4XW!8gxV4Fy@?F}VC2{)Lw;6Lw)kNr6NiKtSXgE~bkc4TPC?hZjltQKBXGyH@O$1dd1Cpx{T zY+djC%(W8Sd^_q^kGpd)v*!`Leds*`S)eJ1;TFIbx&+d-8T%_X9odeeezYbym@f;!3+mUCsI!2&^ILYC5_@T$wA%?ga9JvmfbCc^K@Yqh* z2dMSrs+@_;nAqKJx8EH`=Cla)v=XdC3R$==k$bjzP#Z^te-d;;3Rbrnk$wy8(oW9@ zo?YF1ygaW3)zvXx8+;qP7vn0)5iwwqeLA9>d5Hf`!U)5zGVJxKLMCSoctA_x8CUyn9ikQ9=+4Hdyv7= zT5|9$GQ4}`#{YHXX!wk(w#j{`yUo2BJiHZ=)He8QJKP=aSFwBOEq5n!MY{wSWmhpQ z9xHsgG1xCK5!s{3;4Zhvk7{GwQwS-a4o;irStPiv(o^M0g6A5stB!oP7Wo14-sP&= z^nWYm%diV18A(M}p#rlOw!H;e<_%bnO<0f3SdVtB$Bsw`wCY<}m0gi8tjkZZJGscx zTcac3rAz z?7*I}oyZJ+fY`4qx(D^-KC#@GC1#C{iP;c=PevWykNQ+$Y&tSFi()6iv#*M!;1#UF z&V+Tbi-e8K5&IIDn{XpD5l2mi(o5OpE+Os_nuOXj^-J7Q2=BiH{{Km$f|Y_N%<9!T zRBbMY=4plQxf9xFl5?`t4O^!L;{D~k^}po zayACMHW_>t24_u16=xo*Hfuy(W(Q=y3;V1#V@+O-@C6(Crt6VWSTCacw*nssee)A6 zYhKVQbj@U;X$qldxcl4&{9VY}bsS8Xt=H3exwROwE9Yq+z5K0PnynPJGL0KkYy(zweIEgp13p zGClJ=d=0_e4){woJ`?V5FTojNxR)1cVwtUJIFdkIc(=}KnTt~g(Z8fuKdW{EUTH@= zQMmrCi!hID_;Wso0?5C0SW zTPiiJ!2q}5<2eD_UWoP!;(PJc{GD+A1Uh*vKDYXuCJ z6ITw#-vRh~Kyv(frqpuq7sf+)0O;3TtKo~l^2!0fG~?w$EQz>?(*Zb-f8ff|a&tOU z8on1SuNlJ@}hjc zbfJbfl+#zPB*mYZqHy$9!CseOe3v4x2II$<4u0ENt7XaRf_#zS5-WBlzghIXpT(Z{nFe z!}*80EKn{-s=vvP8}7>*Uv4{Kh^&rbF1erY6KAegz`GS_qC9QJy9SwNtsQW{0Bc*a z`qwOgCm7n}fOYwR{FvGSSET!gJ$WEwU&u3MfCsB~!p(s54EQ_`ZI}V(nLR@d_#J?o z6ioV;JP7_IA#ve!3MzY#zFDBhP-_|y+&fU7Vt$&c`N z9@DhAYA3&09>=~$1AYl$j{$B0+=AzEZo=Pb&-iDQXYEOxi<`#pYf1rrP2tnOEO{FK z24G%&wY;1aqhU29$p6n>HQtn_2jNfgQ4^hhgwH+Pgzs{suiTx0=cGX`*Wxmz?K{U9 z2fR)_kI(OLn+*2KWU|3IE>!p9d<0Vp@Q-QyJZs8l(v}R+_0dpkXFm4afsmwSKlEpr z%CA?*3;wLR74SqnnC&^x_dFkp@c=K%Um2X~6D0^5_BFKwemf0Ae;#MTsSolpYuq zbUVfa<+LAe@oSV&x;XHKfbgwrE*82!!0`YHTe_{Sx4OgQIV-rpbkv3;&7 z9k1jdKH}L|I={icHn#$91U~r_m0zoEfl1q>?ic>~LX!rTQS=>V?OK$f7ySV{&OKYA z#zXn%IcRTVF>nj+$+I?}Y|;i35HQ+tKAKTJ+5vxtHi76s!iIT z#N9&Q=kWYAkVIUJ@9fJ>ICosbw>|hgOAaUBD4Wq|e>2ZkGr~Bm8{bKg7u$K(n=K7P z)&M8cu*rl&r_=mSdF6R>HHLPcEoX#z&fM|ocJv4Mlr-Gm%=6@oFwdjY^%tkbv*~pE zN4TR~JI|ssw)0#%V}CrO&Io^1Z!+rp+?^>wz|Eeyr%GCLaV~( z{4H059~6EL;{7%RLzG|M&t0D_*FxVYe8xXE1dCS!CQXI?01T5SF0NndI+J!r8iqXt zdLbOi6^A$dxmlMCul=e^2YfdX*`a8TVTK@x^?=WeP10Dnz@xC<<;tdh4 z^8d;J8`eil0nXUBzNQwyZj1*aCC1ayX5ukhX@A##zeyXf`lEg<1ME)2@V@}-{6Lzw zb%Wcp?nk`R4YqAIWyMD&-QZ@xG4w0f6CZK|4E3MBI=Z!6wwOdbO9af^+Vwy-JuDAq z)58YXNRN0oxD4ee4%<9N#eKc2oH@!!KHZH8Ju!OxGFw9TrW z{9%7I8}0(!jCS4v`kPZ*CI3r!kk#^-NrTHFF5ce(c!=ts{(l#k!#F?76WOpG@C3vC zW!>Nwz~n9cY6YyzGv%cn@J!Vx{FQc-7B|2pfX5i-t374X)?hJ<@mnA5P;2uxlNJDj z{)l_bus`k3nsC;Vyu@wKK}r=&8*Tx-zk>OX4X|$Sz_mFa?8sV=T>JAT?JC@ui)Wf? z|2riA=8HZCYL=Hw+6`zU4^w{IUNLEB0zLq6Uu~Gx{+b*weI8+NcXHJvKH>H^_{AW3 zA)jEjK=_eT5f}SR0oL^~;SRvZ$Yx(%5yWJQqac#_RHa!nYSu@v8(w#X>Zsc^n*BGk@iQ2dDfy4=Gl)%nCC#w zNaObvXF?ibo)2k+dER4z!Z%|?JSWoFAJ31RtJ;wt)p&lS5$0KvIzIddajvAXo##hV zpUU~>nRu4uk$`#8-{ZNG=i~3G_|J??CeM+q!QZqweYMeMo;7J4AJ3gM!aS>z{wwK? z{D(NV(g^bm%R<%8@$tM%eY~V^dpDS8XByjij;0ajnVLqJ=VlsVo}p=kdFG~(9y~MC zIDVdQspE5eJnPa3^W4jMYW&Fmh%+!x1iZiaZGSV*!z@q3L(M!Jb2ebG80gX0%yTl0 z{qf99Bh2$NjWEy9G{QVr(+KlyO(V>6HjOaP+%(RY=fa+XvGAgQ$#Y?iFwclhp^fu_ z{WbASS-n5556^uy!aM`k2=gph>Tk*ipTP5AnG4|j;a{71My!$lcs}j*XoE@s4*Ufp|761V%=9ZKrx|cY|Mtsi2mB(MIRwgc z%g?6lco{Htk$&+UUOhhKJsp4pdLzbT*=y3S0L(U0v<>iL@wfcV@{9Dx_4XFP)GxgF zTPI*$zfuRpF{<%w#Kra$V9Gxzk2Qd&Wim4*EnskkH3j&;fTa(Sqm@CK*8`T%>u>4= zyaJ7k=@~!REM_eT12h~6eP9pDMwkupH}WxnuK_ME#Lw3FNOL-WnuGgWMwzvD(8m68 z+M_i!+AR2k_>9ln0mDU6?G|hESTnxMCSiVCuM6;f;$Gqp$Z0#soX*#9{W}03%+7EL z%QQ~z=OsU)f1HzSPScbw-z%7DyY#(TCI}B&H;`d|mANbE)GRZ9DmA8<; z+X0U!fQ#$f1$ZO+UxUBRpnogQe9p`l`~^7U-vA#l$HGheJK2n{*XdV(a~!aq?_zry z;PrYlo?mt}qWmI z7iIUi^*C^$dLI7CePLF1`{{GD;dVIhTNFO^WAn*omRy95!1zsFfOi94i~o>sHg}$q zp(pL9r4&|E-B0@10qgYUdXxdC|IapIUjXa$CI55+J``=dxc-(hv+xgz&v>g0FnNn@ zw6AS|DW8Pt-`dK}26^wBlLE}K^1@85uT^Ewx3)GLw%4H-i7xoZ57?$)j$sjC-Jj&o zWrlXb&46|OAiSX){>^~(d>`941J?69n4xJ0VBMd?h|PPt!4p&Fbo|Tl_yPObk+=|l z0&dYk#2eaa+0U~8o}O+un@=*dv%h3Fcn#oP_?*@wuF3a@Cc6&FtgfIriu`%9FT`pm{-ut_T5iJEcrT(UV?=-Y?{j^P)c2Lcn0&D|5FV3L_a0~u* z3V4v&cAHs?<8NMUF9Y0+fB7tMW=7)WxB+H+vjJxR z?SO6gmlyl&G_>~x{cU$<`lCH41Kg@`nXl09$~>R=C4l2-=Y@9U12XNrzZ0-UHjDr7 z0$iqGwwK(UX&3Vc+=+HMU+k~glHFd?4K4$$(}VG&`+;ovDFb{``gtbPR>1UMd5L)Q z!OZyaRC5Vn`uh4+3D`LPM!-qcDCP%vl8%gjb00Dbe~5FTyj<`oqQW#ja{quQ81Nqk ze4c_?kJ_{~Tfct{n6^${xkv_NmE+k6c#(n;zgd3-T%cg~AAKUDpGo7{ zfSIr2c)8!@-gek`G!X8Cdg41zWyVAO{ViZS{^doSL!ZvrYr-DD#`)d^*k}*70H(jp zi}q04ma(54|9ZevWi#5%=C=WlS1|kE3%Du`ga7`X86SSmy#%l~-M*jc8NlNd%zqAj z2D>;6up2N^reZG0R9H7|H)}gpJLg;UY(`%A{3^hgsCLrtC%{Jka{6;ddjtOh@Dzp5 z{V+pzm_Jk?~{NNX*f6M z&`-?xo_FS21Ne{(sND$IINmz}cd2%ccf+Td{!od?G3_?WM{dq}fbUZHTyN)p5ih0h zH<^-v@5K1Y+uW};;`7XUlOKuz>;58ds|8GbFX=VN^eo`#6pYHd^~f*G8d8Mf>WldG zn=j4jdK`bYe`OZ&I}r%i0iH-eTsfvy-(=6X>07h*v})yg`oGKQU-DPY_t|jZNBF-` z9Mm2Cz&|xVXSe?iF!M#cc>nOd8GQ+TZ(0I)lx!B`TLbtg1v6g%?3awZ@aHk;nc-@f!>;S6X3Jb?Pl|Mb52?x^ZvQHIl|v23itPH1#DdZuKqb9USK;Y zWm=37n(+fKuGh>d_CQNQEr3?AATPZM6-QA?H2lBucbx5~en+Nifmowb>4WFt^u)%> z^2YL`>y~7mA#qyf!EZqvE}k?8J{Jf+kmpRT#7XN49hOn_XPYg34&t^?hyI1*)UQ6V zOvlro%h=Nb=Fdx&H&#>`I>LQAjyBe;wc)vW6?IEjR@Y`8X25A3glE7==2a$>`@$(h z-XlJQI6Nr~So&ZL{n&S)b`52pw=CJTuYNj^-!8*FvY!B$@V(e5*89V2y|ROOOh zo|U}UB)*}YhZ9k&{obs3_0j2w+S7C>cha%!uO_WsJOe)SbtbJ#{oZngN$czfL*0PW zSPjq7*CMsfq}f0l_R)DBzW;`G%fnf&wT%t+jRt(3-tlFqXB?!Dz4tX~Ed~yfW|9W= zsowgrgz+q~CZu;&LzUrK>9HM#exQ6gHp{`FEBHjwwLEFS*T-fbi@EmIagt5O`RX_w zeb5gAK-tgI+J;{GY26IH1fwT$%2U+_9DQufw`-bB@q5dy$YVfZ4ge2V*JU_If2Qps ztO=9|*K%Kc)3y#c=p(lgN0=I(r}tqwThm&xj{R{mWQ74iZ?~<&7;@2`sIRZ9&vc-- z+n1r;g8r*(m)2#Pp`D~mmMwm)%df`Oe6u;t*YO+RdtrSCfQU8e z-ZFh@SD_s=$gQbvsL;DYPkLW*#Io(+Z}L;u`S2q!-+tvOHLOfO&F3Jd#B&BVG}hxx zI3>c&rb=41orM@t_0x8y{DXbACGj_O$iP&hz9BuE%=4RZ^`z`RvFBx6+jyJ3Ciey~_&q0(U$l%`upNu|74|-rI)oB^b;E(AtYkvg&y3@O^0n3us zCK>$C-~%#eJ&JOeSwk-Qk`-yq&1^}*g0VgM2sGx2;u*T>x?(N*RlJdeh-G?iN+#V}pN4K$7aOiM#di1&GGqbh{eU7fEt6h<(Z_Es0Wkq9M zjWXq#F)RT-{m1UK#xcl^jjMakaX9dg=a{?k2@ghI(zLY4cxvFU-wyl{-S{NgkVvG} z20TY0FWKu+n{1#nr_>v8bUFtRL(pd)k&R!z66b!VRfhq;1~Cj=qaI^eSzq0l7B2(7 zpD`2q@6Oves8dNxW-LvJx1R2e9!sl}z0u=b#B*N)zX!c>)~%7JbephB#edYv-@Iqm z&c(RK)+~`)IkUJmODY<10BO(lx}p<2hI_{J;A6VV^~-cFqxY`U_t|%3jMVEriP{yt z(YOfroP#m;yr%;4Qqz!jo(<1QB3JMh*Cl%$VLcnFd&A%GChQvJyr&*pR$sTW2k+ha zhKxOX!EdOq&^@}$yl(_P^=fxLnJQm`W3YSD_L|pO0qJ!-hQ(C8ojqnA@S0Jp2AYgR zpNz*xc9SnGc%ocaAqM=l|B~_ih;I0$8AXiVGIM<4HPi?3OAq;NSlX-nmi^PrEr~tG zvvg^)DdQAp`kw@RKl<-(M@o4fa9SWT_=#7g{B+k3!Zzq6$M~rqddv|rmS%aw9BW=N zi|-C%jyN^f=mXU7wDcZ)GU#QQ!|BF{&?lg}VGO!|HS;C2_9R9GvS>%+Z=3piFPtv= zrM=5QMq3-#<8IsoS6&;D%^&ilXv4F0*$loYzpK~H9zbSREls9Jo1yQP7i2zpcx9cz z9RrY7l^NV)kZYtJ?P>EGt1BDJmt=^Yxu^d&V5i+3yJ>kLm0D?&JHu2d1>wdt#57k+12w z7E7}XU1nSzs5LOx-CeFAs=A@WEHn7ifxmViYmsrDV zsU`J2VvA>hKOXqQv+bL~nq>N3x&?W4%%vCEtKxDbR`ndu+ndc|ullfT-9b&OkTd|@RqRl1aB z@JoQt+Ho*OW$wQR{yuz-jlidF?XK&W8r7s>JR0yf0>8U0S(@bx{93Hw z=)w2r-6j30p1KzSl+k6zIOt<^lT`ysh7R-4CsIbeu6u2TuBbBjcHmFkCw@vrSsDC? z?=XvZB*9nR{Fg?d3*;m-_!j{G81z3ZThG*&*XqH22G;>x?jP>HCRwJ-uqI`AsXOR)E$1cKT_e!#Ct)hQ*0L>Ja2@A#>6W=@@s zJTr7Vevvy+e*gVcehTiqCUi+fWi$+5~Y9h^BmdtFfph6rK{#98pX7V>gv#ic*Y3eA}y^Aj$w0GJnkyV zUvPRp|H_wi8F;ZA!;si%O&FMLo1H&((W1qI?(Kh;bOYTL;TDu9t5IJ}OlzvGXv7vj z4evbUnDKUqc+Qcr(?Pjn>@PTdZev|N3wiO(UvcMxdDuF@W{iMy=(tAGZ#ZaMnW(5n zk)Sk%{Rxew$wck4#;PNLWB;d|=Ru_v$?DR|C0KN^M1_f^iF#55#k#n|D{1C3duef>o#q!#QS5`ys=eBP8v^}j_qs<%Io>9t{ih$ ztHc{4>61)gYiR-YdQ_vTIyWH*6la~5&u)ED&SQkc#V(dYZunf5NX*0zHZU9TNE162 zo$EeKt$RsbLt~oHF;DgtR=hD<;YjYmeCp~~&uuJkY-)(RIlj&^Ili&o<~x_wnDnIO zbDP&lT&o;kc|!v>^-L?TPBzsibo!OtkVO~Fp`fY09$REf>gy^Xi^YXiWT0N zmA~HKaSE^YH}H;Ec&mPcE;AI~v%i5i6L?2seQj6E@lA-u?5EGIsIN;(%|%kjDeMce zTd;0rJPv%y>9v_(Uh2;sD%)cH*L%0Ge*Z^?sPZlrJsWJk1wWf{3%EBIrOT~Z*!B0L<)L~ zGGIGIK2PzYE`L*+mO|zs8$v%qu~E0_y0<0BXYO?b87VOVbzq2$d>RyNe`G4-}8k&s-1x}&+TMm*q zY280vsCF^Gcz@aNP$_Fm>*{OD8>e!=GJBA=qY8Ty`HVrO-0@b5ErwMnvm_IoTgl7% zdSrAWmZJbtjY>|%@`9>zY;B6WxgOdrQf4!HN6G9wQR1P<3Z2q|m#_Ie;7Q#%zm~hW z8yXY!^RN?}CRWhh)++G^3%X)|{EVi?#474A>`}q$L=F0wuOOpg96s|y;dK`lVMzSx)Yopx*la@Cn8^PeTe=vWUAM%02 zKKZUf)_N9VJD_1biL>cE+2>IC+~w7&!s*jF?S}c#lyc2lY~9v;W@1$%D93f^Qv5JN z_XV=;PMW@jO1BTr*nMHcXh=8?zSqvNTJ`xC<6V^U+KL3ntmbo(>I3p4WDBfUo-7jV zBm4)Br}3|{pOLXsVR4}exb0jhfg+&=usmYD*LE9E5d>smc~US0$FoMsF?d-#t1yvF zG$v*(ITiMg52LK|+u&C}<6tdOXb@ay=DZQY)+pc=ca z%9Fw$qT!@IvCNat*k8AG^J|w@uaa%8c-Mi?hb+;LseqxbpT;d+xNm$a&9C+>{(}N7 zT7s166 z!^`N9B)-B__GX|J8H|~T^<9z#_@mupPs7uz>r|0|EHEH~P%r}ES1pRaW z66xJngoS)HCC9wspYpeUh!wfRl)tsCm%r_YXav5w;04Rc@;7PkgeH~x80$>_wW43@ zt`RbqT3S4#WY(N{rG-=H7R)IwnKx?=+Sqp#dtf+*{YsZ|7mN6fd)E0I#vrz-lQ+t+ z>7Bnp7eZHoFX%TE)HT&MO1?^U_bY@JbmLu0+^hV-V`7zc1$c)68eei1NOwX$^}4Pt z_-DpBKvOHeN^8DH=VOT1suBu3+7!yhrrpsjK zfwEu3Gt|A@uS`)v3{}ne5HvQU$9-ROE*BrmIYUfNy^L;6r0h=}uqZn#2@ z#TtuENt1Z?l&K~8jH=}ta{o8S0-Z1N24bV0YFmZ5Le=T^Aitt#I1K%J_o_Mb9966_(6#thx@mM#UlXk(6Luk`5+?ei8cVT8Zyu3^cQ@v3lw1nRuT{*vEKL zi60za6pv3eGJkwA7DJEmIxd&@BVH*zRy{AH*CZl&@T?0Yo+Y*n;}TgEOfIvg-*?G& z_#bp|gu`B2Dg6N(d=$>Oj2lo{@DpI%*?5SoVVE!d3DcHPg< zL>E%;ZCCt(T#9bfr>?4QECB7LRA4+#^wYdi(&dmC@|O}*0#f5@nRkm-?h zXjgHh)VHNIjAmp$MLuJ*nx~$R0ng4|4V5T@Jt#SjTwj z?TYvHehT20HR_~t%6@iZ({DyD;nvqT7Ug$~)4Dv8o+H(=)mB2&i|5fZZB&g5?!LjZdp(dwWRs zp=fAmm(PfwE636!SBWekV}7nVKMu_CjyCw2SkAd{jv7}_Ppn8J<8IQLXByGo8g$6? z@&*|_>oHPmH$8)XGC0a#ZL`Ve<|{>0L*;xiSE0LW%2y#y zZs0Ntzt28F_A7G9qF*7qPR`MSfn#Xe2>SPz^JgiQ{reN-YO8AbJX*D$ag1~ zO=)Uutg98$4h8EcUYqipM#Mx&1GQwxwJl<{OZKNJpJ9~luhRcw>{>aus+{T?Gv_+y zD!OH22eBcRrd;qazj?(q8cv^9s^*NUgl|jQwdCmY7Yy21UtW=*VxWIw`-^Ow97DdN zqAFfcS6hi}X#T82qF$T&oI%|vW2>7?3ajUuaE)J7}?k{8syoB7PF!NcwL|HF8PT`3Ds!PpH)Z;zZ zIf({%KuR905K_vio@bG~CS4!O4C!F6ko_oLtEJSDMieT_OZ*PG1}Zlp;!$c7@`I-G z2$N$C$cU8J*EICVBZw4$%BU6L5;D(zyT-}qrgfM~+K{d#cgttWxe4~4hfKdLh^cY& za0rl#Iv-BEVDZwLC8ZTm7!8bPuEPFqu3z6$ZvU1s4)^)&sl`?5ZlZFf>bzR3cIcE49nK;tmLot}-+*&bWjl8m)v*|AOK^_2)*WIig@qrJEmSw7Luv7Ds%NY7a)-?+FIvo1!CmN@|8 zwJKW$Urg}Ze15HAL3Lu~tflBn*77*__+xTzpmk<$jQgONMf)qVJuOQrWx+Y~IrdlO zoQ*k}EdIxMnB%c1+cc1NK;*|%+@pLX#iJB-_=3wD=p>2$*U9HApHinOAHigL+L!8l zPv>>!rSaYjrl9jW*Jih(hpwBY4CjTD^Ksg>6}Dg0N6`uUD8UQ1l(#It zp=}4>un)J=Pw#O^eKKl_Q&mlCu~2<=Nf!e!8$OE zX&;r}QX(y6yaJaDPG3=eNqHljSMqS1l386A1u@fZ6+Iv+BiogKH=wk%yk<#qIdrkm z)BN@W`$v9LC`>)T<2q2zIajH>Dj2XSuf0+{yQEB)PqmiHuv37jRCJ!Ouhr#;p$aCD z&onCw`f?6Q#b3JKfx?y|bbMm<)K&R$7xK?s_qWts^>wFWXCX&rRNd4CMUTk&4CFi; zmX#*pp63{{jKW_i#=O#jq_LcdU{b8RikC<0 zsvfFOtbLjf%afFY_}Ov|#|pd#)OWx;s9e`Bli{9NiUuhM57tW>jf*Wq1_SAR3Y5Y7 z&d*0!TexU3=l6kHvuvFw($*9~K?BP|*`Kt>GWrzHL&BMSDP&#g?!naEO8+Z&O5{zt zZjyc2L>|KsWh*yPgxVtOA^i?NbOT6Fe#Szx8Z2JC7Bp-2%DzUVmpUuPoKk@MNZVH5 zuj0)u0v>6=@BL7Apws>7lnw?|1f@=$PMq9*=qd_02}Pf#7TAn`dAbn!G;N4SBhFFzWgBw$o|84zl`@{9mOar|;XRed~T7>1$8P`(!M@ zPY}(kI38et3Be!UW_iIi~d&>SLnX6Cdb9<+wrez0&70=ZrmCaI zg|Cf>v>JYAKNjUX%kk=NUS_utaZcWce`b{B5+(1*fit#KJHf=R6sGvZTJsxw9M6Hk z$rqp5E6SIio~)7a0}-m-(OY|GrE1q@Y+hX>c3mNIiu*av5o-N(y{q_9a2$P2^4m`; z*AAV*+N;d9t6B@&+uxDT7%Xy9SU6>ts0jFs2kN8@W1h^1=<`If8p#kvQ+}6`^N_xa zjGR`I0h@7)P-+PW$h+Za5`S z!s-2MDqfkjLed2L!+OnC==;Npn|>Wn-ye4EZ{X?s!#;2P_5SqzVc+~le|qg;>dIg5 zPv0MQ)o1|;u-a&dw?AIDx3VnjSHX?g`MT`Ee`K)eG*>$88GP$ws3A~>f|3yHU{m^X zw8$N@2%Cs!=}nZXI8MH6Od9N8I;RmEj!7O-#G~)eZ`!a8``{WH>+#`-YWZw_+mXKy ziLHvCUQ%8y9q#;mZek&yb)?%@_h={Gd3HAMm-e<)H!Pf>*o?!{-wGK?c(IOY<3<()#47p%*Hp{45wY^hQ=1yFQ(9-9;{2(Sbp#LCN6UF0ApK@cPp+@v zkFl~%=1Q09ZK5vlRh8aAS%NiQDO zlhNf+_bSuDTiDL?**RauZw>H%&?3TVF_)CGH~Z66aHVK&Kx5Ln4Bsl{z0$tRG&Cpy zFJ+PNNtloBXqGq=V@v8#xTu*^y{rm^o5O8kYAf*RYFCP|mBaRt3*hs*$|P?LRnry< zhGXI$RW+u<-t9fqQ2lE9m!#hYWoNNpTkwz!o+&Cbu(<`DiR`bWU&nSypDZ6=d6FMS#$v@r_Kq&4Js!g5;3f3I)y%wU=^e4EcR%F3HIvQbzPH zY1?v%Tpv}7hTkk)NJI5Zu5`n=TeYL47@p!S6v5`(ji z7&L2d2t4%c4I#3A(Fbu9Wg4}Lx&{<`SgYv74-jGghR?=T*HR{V5QC(jB#l0I5q&6q zGzs^>rO}3q_sM#03*-D+&XzQmIv9GEnn5gt)U*74ET03rSqcT+qYfizG%mePR3PQ_ zsAQ!>u4CnRIVY8u!+^yyaj4R7cwNqAl<-^9^D%Ct5bK>fL*l6%EaY3{$I@{O$5J-N zxL);!9h!*-?(N~X**Tx_u_aB_$;z3Dm2pEoGajFt4-2A<23Bup1mu#$8X$7PbZ+IH*;j~NTTu={2Oq`DMRdFM~u*7@paolZGjG)1R zd&uAXW;CAxe-q2k&v=Sg5JZ3WjfVaV={~(fQ5aN8ob_eSt3535k^Vf%DFfPR^>sCT z5rFg3{x10m`}-s_kS7gwHhG8K_(hvKB_aDfNXGi|wGsN%`P{lM>o!-knsQoSWiR)a@Z*hIw&4n~^KxdLrk_e)4Nqlei6HtuI#f1-aIGbwSRx>wGyE zCBr0$nzu}!h(vTksQ#6$8fLrP+9AaGjRy^B8e8&oAJTBc*@z#;#txok$V|tyW zY)fYa_fjwLTeR#4wGsLGS%@8f*VH~@{4PUR{7zo9O_S>*Yfpl$r`BRi17AksebevG z^e1-I;6CydbKaac_S0ZL5I3xH>!Q*d-YM~Ez-d=@EF|q%aV2YrS;h>)ri@upJYM2= zcHit6Uraq&a+MszQL*XoDE`uYDfx~E{bs>lLLcRU zV6>Bm8Q%dF+92)iWxfANooKsF(rqI5dS+RI-uHWwmXnmuMjdITuCzJ7iFh`je@y!M z2#%F2-Vitb9nTsE`9782M-{Ut>#!?$b!~ZmeSP_A$^9ZeA@A93a=hvHghj3}F6wD{ zhH>#!hEwb6#oAl4f3HUALFlhdil(~HEvr7%bFwDiit}K|gOaCG_z?H9QmPaR81;0A zs`(#mu-g<9_##3xp(kvTs;xlqBle9pD*4y*D=-a3i6oq1l?%;NI#=l!5vbq=4_1@K zco+UfjfZ}@p)OD2P~M+ERkn`-FZc4|AH96ClNft0zQ@P8jc5G~1-zP>O*M$A>3$o^ zd2$0Avh6*9v19o_ci(O|<;(8UqDwNvG7M6Ii$v;snwDqqIE=kOuV z(&xq8hJ398+qK2}Ic&Ey>+Q^K^lUHtRO%zpP3j{qA>De!oRppR1@eBGQ)kvfMxFDp zPYmITmpHAvWFOGYvJXRjkI7l$@Z1y9O!{9_nx&v8|SNQ{r$NCPcJe zJX5?tKwclC?42GzK}4jNz(`SWgM9aB(t6p~VKJf4%8}g`5fYWEgPV4zRjh-WTaK|csX|I z=YXVe7%P@@VHmPXn_AW^`?LsCjQSkS2+0o`Q`Rj7lHPJ(7JPHHCZwU|M%kufLR6Qq z82Po8Q2Gh%kjD;Uxn_2?hfIzmg+gBTCNjns{5CSz6?>-8O{Lf~#d87VZ}_ADexrP? zDVM*E6Z_&-sM?E~ByO9^&q!QVE$RiA{M}hE=c4vUuf(f;R3l_}2gPr~fD73wyF>P^e6;l5HeyWSvz2Z0(ckF49hN>MOwyu#?cv96AdaC$ z>9w>T1z#kWQK`iJZxIR+bjd8HsT6)oXB2;PQT6=N=eE+OsZob+&?H#&qqzwI-* z4)6iwK^Mvgs8aQ1#BvGoCXBu_)g}A2wdu5=>xZ>A__Ulyv(mqk9~;x#@#yc8r_0Wg z>mYqD?0n_E1DaqKU=@!2?B3*`W0yrO@~&OU=!n=-v5@kU4EWqd+*y)8A8&svIyCoU ze>iGAKhS=Pq=OtU=)|`H#Tz|xzB{gwV*y`ff7=25lSf-0k@P?=PU<`klh($NS;014 zf$LVb61XC7huns&o^k&AW+vcretw0k*Ut-^Qb~M|L4V02?!-cq_eyS-}pTI((sGwcx3^ zigQ&=kJVL3Sc(Q@i`Y*}?IRtg%a0)zq>UrZHKm(I;oTo**#WW(l_=g+dR6#FN`up#5psO9iUkI9ebocFs`OV# z^UcQgOy)1$&hws0S8=~mp~m~$6#t;zVaOb&?|0vtz8|&WbY@Ys(~qr!ZiMRMx?x{! z6O1Gd5W?^45sUG6?&Dz{V`h9>K5_zF+wx_#jWIXAd!tGkV*Ss4EWQ5n^ZU?8mkNdKX zg+{S0U(mZl@uTuDjfz#Yw+7{SRXrlTzl?^6>t$2>%ako)A-!Tj-Vhnj=FhaD4`Dsxxd zT`GsG%kBm+l947dZWAv;KysyBSv%hNOsFQ|D`oaOb6jhB#Yv}og`Ti1)$NngZ(=`& z43KDB#Tr%UI*CqK#vT_Ws==Vk1dc5`J_ny*eZ=Q?;8dd@(mJK;jmT3tel=bjFUKU_ z@zINg=z}~zF%y>worzdd_h#Z)QJ6qx{SO zJ{m+t$T7D(FKM7^ujDxqL5cV5p}Up|tm642` z4ELY9vCzcpKz|&U{%#-yG28k;B8j(1@v@EwUhak`zaL;fUeaZM;j42BbW^1~@w`;Uwq}{@7m5$)3^>^1S3P;-HY@7zrjcZC@(YWrw!Y3iB zD-s2G0}wczU+3#m)}T*~{sv99h%tDs9_faA$x0T%FT%&>ckZwQM0|gP^3eJ}82c8un(zPr_ok8iK?s@9CWO$4wRKR` z7S0GEt8|~#(S_VY2+gv!E!Q-~DK<8gC-G*nG^yk@aWk9QQEaR&iSZ51xF#%+SicIQk(dTtuZ z&pEcm<&RLSP~Q`@*`Qh;fTZ_q?wfhS+_&tP^4lo(U{HIdUiFNL#^H_6*eN9!B+Rvk zXikN%F>KmMc#{B7*}mk0gJYzor&F=|_iR45Zg;RfQ z)k=t=SVd?k7U$y0@ul2qdN*Tf4Vk_R-obtU zUwPS~+_?pisCKI`wuUvLWMal&74t>$(~4_3K0(oQ8_B7z{A(AI%4ZF71i z`A#Hj=zLLvE!IU*I*X1JRrUsMQjFcs6WhO0(@!PnQDLosez;HVdq6jl4C6S37Dfh_ zvI#8lnqh5Y5l$&y3cQdE%<$sAIZuiX>xK`3SBOS+4;Z#AgJd2c>L)UR`fJ1!#_a?f ztlL3E7!xH+TEbv1KeO{3Pws)aSVeztS*k*gqr8;q%PhmmtbhzB+9}K8amd_dZJ~;s zLnM$APO?AY_9advXRs@*7Nay~kvNg^@Hg(utgA^Baaz3WFGKn1a_@&Gb=C7X31&*S z_3(tO4UZ3{dVW-d11`3?x}S`{T5c$Q?I+teeb4=yZ6RNCG^ab!!=BwdANZ=VQEKUn z?w1eJ?vuVE-!Cqd?whLI=cO{K`^GZdlzxUM%Q}IcnX2QfUl?u33@7-cOycj<`kLH( zfF%3zl-o}5c09o2N39Z;Ga8JnVoBb~a(k4VZ;FZuvTm2dmgXANkEG#Z6_1Ck=k_e| z`0lhnm}oIdOWZNeQiBJ z<;g>>B~q#$i3HhxnHnd<3?V8@2_E@8ORbGUmXm=Vlz^~XA}`BJnCfD9e9aKo@HU%W z5TcMBw6aWmY5k5?i$$-uyd!xxu|MAhANA~1Ec4~=I2)HHR?I_VH(HdVEF{5 z4#B=>X?C(+qIa@;sM8O`?)v!#16Rqh$5(mgsK((>L!Bi++k`zfL9yEs<8H%-bQRriK{ zNs7Gqgd{|<8yT?WrGsz zmn!)I*iTI4)eBex6A}pa;s?f^z4TWSLb3A++n(gAHDzy8I6~^vG-boI_Jq+%iBZNR zql{E?*>e7~pYlIGW;ns8rJ7w5B@5bLdTN zZOQDF)7Sr5^C+diodAV&aL<8&|F&>E4NV_F zxLc}sz@UUFDCu8UMW>KBW}VU(V+vlwWAEZgMdQ5U1|(uJRu;1y-46+$*0Er7bw=WI z!Ux2(^tY4_(3~kbS6y#zmvs<<+d4Zsq5pu_n7-ZSFwgT^nuMK}vztINuUO@$a4e4^ zk#frf(o;ssWN-P-AdQc-*Z^7PWI`g$3y9+WdaalAPd zi1pk(^EdJ&Ytl`G!}4`J|8CFAF-9~^nvm5}^Xc0{%eT+<>!0l-)6_E(N)yGPwd#$^ zAtw9=|HgeOTIBfGP@{yYX#>kM?U~vmA#DVsviqG-a~@JnZF}W)yQ(oV88(vkBAsVV zGOs4K>SK$tyl1D@YbZ7y5+4~#HlKafdz0y27Gx{wxR)T$j3;&vQQzU zWfW9X*qRj3kD+#)&+#|tFFZazHjeHMRdyHdMI>T30Np!#+5BDt-6L4hdErWD3_vYd z-yy#Kl1~WMc1mt*b^zocwmMo00udh=6wDkvk@F4tMx0g!R{c4iNHl_}%D!0cYHMn8 z?MBM&4CU{z-zNPX$(ayc5_c|2@1jmI_(++HU?Q3a8AHTl{7i)m@QieyQPs-r>=_@2 zj3?;`C^H$cSBN?-r*s72At{2ACa6vQ%T;IvzD!&w=?IeFIup|v>2+^wv&PB?tp47h z|M6Zn-+1d8X_Nj^OrqZA6WWu!25_MueW*V;>bhinVB0?_3P!cfi+GbG5OT ziu`FBxBlsQap^{^5z)BED|=JMsZ_MtEE^2Hl+DR`-x3bjdEDkWm11Mk0rdQ~qHWNk zw53#yZm$7qy)R5X*J2HHw#hp#t%kZUhcTm_w+wfvZdXDgqI*V)bbA?1no4w*_?Jywng(%B$n+hid} zk>-c^JVL$LC;>f3w3@w`zg2r15|Q0MjJG|?Frj>>xE0f?$_D^*!3>uNWB_GRTvt@= zlan-^OYsjfLmi=VxvUcnAuOPxKoA;lXszab;0uKUhLi0)iD`Z1+jq z)AbnR(JbfU{o^>UOI2OC55v>?OM3Q1dxR6cqCMt4Y2R`aa0-a7jB0hOI4p0fM=b1JV}foP8z$8cShEYdIjtGFU1&j78Fq;z3P5 zf`?Tm22Ti?}jOuXz$APbn>yG^ME0WSkP7g zj{mG?{B^X%-Kq$-rz3AHr(K_A4-xq?V|yo~eUdM?S1^5Hy(fgCmO=4}u>_BDdxj@` zCnAglW5|?PY(bOy;nm&ztR7&EaFvR}@Us+M)$h2lHSyK|UH^~pWUqLS3HltsglcUQSTORgxVnMl>EYD!Ixq1p`~15Jcn1swS&2cTgkk7h)bY zrt*AHH~;DV{L-&t9y6%D=2c`3#n8|Y!A?}wS>egXBpeS#8`DrK0&}K0r9c@FpPTDf zV=z@pNvgc4-F0Uq=AcF@OWKkSu)2g6&FCnWi}?!W0}4ev@Gn@7bk$Wr6dZ0BLL zDq}u#gV*T@o`s4>m3{|Wk?7%@Ak950vqg5l34tY=!_)D9=@M6o{}cT0DmkXb8VM#` zTY!~0YP7l%>@ustjyhGSmar@Is|FwC^U$}3{ycxB$CDB%gyzkJPxCS;-KhD^%4a}9 zHr}*E<4jXDtHtXuP8hKhu*jj*ECeXE6|Cl??PrAe8xRwh5RO_p6e5>2_oN!U(H1ge zaqg6nh=Juc$x1gBgMckI#TYr!NJ**$W8FrcPkSpFP4HLinN`+BXiTeAf7+M`b`}eq z$DIMB5#AWLicYNZ@d=Km@3`M8{3iD%a9(4uf{~_YaDEHo#Z^Mb5(bhNp2fS87q}+b zYZjT`6Ea`hx-aFFz75lum0jjLkqXX%4+&Sn^LZ{Tr=-t-#3WMQlx$7@HOssC*SHGPo zx@NU^V2TocG8Nnad#TGIf*5GDLWC?inv)3RcI+)NZb`xHF3A)Zsb5!J8(_xP?VoFd42RdAy5H@v{lL0@F6OLC^| zxs>+U4{)G1j4EH&0!xj1`T1!D1I=zjBdCT*M9ubd9&n0RARD8wbQ+2%30F|1cv1O6 z?!P0|GFL7KTy}{Iwkj=ABWw;g*99$8`vNTZ<7!8gRf4dCjL!yt#BlGb&K0N<68ax& zF@&?kwmfDnHjM=MYT0z`N0?OJT^o+I%5!Lkg%d%KLfwc+R#*{ipPQ5)$+S;2IfBk9 zmIj=plR7+J$WzPO8j-DB5#fUMBwI3+Yyq7>iA|6NS_w}j>YNa5e+-`^%d(iBN8vmU z!UOlO!608>HD|-laa(WtjMh*7zMeTH{CpfE);C4;f?(ET#YecB5z?-PBM*XMB|ubw7H0*&aC<&|7e<~;JuYI+d-%m+70E05<`+@A4i zws(k5mAykX#z-HT?Hzii=A>7?KhqdOH0ITAws&Gec#QD9dRlxlRv|>g28(ajlM?3G zH{<6t_wMKTd+jtL$r9l;9N;jGO49@Ed;K+=|bw zx3W;*S%4ey7oVXRoiK8Mo22jTnfxu+*TONf35l{kgiv|uRpLVAZ!1`pDS5&CO!8uQ z64Dc7;WC<&i=vMv9NRNQCy$B_L$HQodxY=UJ<49pp_(mxAtXFXlFCsoY1B38JGlzd!>gP;4@GD$O@t z=^B=E5dG$4Fg~%D+I(W2?}Tl4bg$h}tsg{WW2VFtE_G_{k89If$e4^bdTwma<1o`) zKXQb?xQgcVY9E66ImtbeZg@^Ke)2l^37G?hP11$$?>xizZRNSd#>6o7{ztd0-^a?T zdcV2aFU{{`Aqekx`v36z@tXV}(f0{Ocbf0xo4}%MWOPC_oiR`Hn8uv1*0Ru-F_co0 zaG!AEqvnG3$$DVG;mlUyS+<)-euLyhPHCcBY0-$v?`W2Y{coWme9qc99w0SZ71nK+QG7 zf(NBZS$^qM9xdBTFaW`Jv|7(Q2$?pXcxn1w5TV=L7;tfl?5^fh6Zr10NK*m(b&8PQ*RUDSr6Ogxq zd}J8{lct{(4J?n;nPJs(TjUN#bB2-P2fsNyXSRuu0}H_qU5qU@3=@NT&icyw`6$*4 zDz^mjz94T2E$hrwr8}(N8T5>Ih%XIy83rtaA+O}hDbm=n2uja0syqi}SUo2hQg)na z3U<83c9fH4xKJA@qZl9DhpH??2UDRu6FnEN^K+gn#vr8}$vt%jVFzuDl0G!&OhTW& z*SF{A&=X`|XwRkPB<1>19wU4u%NT*^fjDS@*n{4259TqTRulG5lh0?~E-r(0LsvIM34|H%ZsE z3VAHHK_F_Ja!WFj#^@Tr_nDWtdseAj=ZfAbug|bRT;iF6fW~)suhu^J#yCEn^gi9E zy6rS>*uzN^VW0t!QYMwFcp{x0_PjG_%pwW;tOwdEwtVy4Jd^6*=;rab(9g5Ku@-&k zrx*G$e#+l;G|NjeEvubK?V}Z<;u=zt=Bm)7v~h?aK$N_{D!E$xgG@=a8%xS}g0)`l z`)!6ZIODLt!BQTS3-(YyrZSCo=?o%>4aplUmI>xnYaB;oiyPxQ13nSVENy*<>_$K2 znP3f+javFv)5D-MAQFlg?~`5Ruk@2<7s*3X`%lI3j&U&5kn9+ODxZ);)P}Rlg|?+8 z;N4RjbpIcv7fXKb|4kfBbeH%v1@TPu&L$z6*wp~hPJ;k&S7gu$Yd&LGJ2w2s zz>N7XSJ{)={$46sO}Yrngj{VGv1HPdrtaLns*McLb3}mpD2Bm6cwt$C=1IzMY*c=0 z>&2ZGh_e`6Rq~wWKQXnWEPAI44ZOtD1f=Rf{FDWTRlL-i>I>_&$%tf%95< zxdMct7C#W_<8$VSHcY+@L(nr+0qW9VywrxHqARUV0oNX=zM{{2X-X8GXudM9lhM+( z<%Cp_qMRS?X~giU#-GglqZpPYGl~25QL$pJJ@Qldq)oG%BKJcOj&-Vz4X^pC^p0?* zdv}X`re&}QNGrB?RIOv)vlEIm_G;yyu%=IK7eBRcJqi1=*xv9{W7O72HJg@%9r2|O zFu_z+te4NF#WDsVNt$v5ErOBfo94CHCN|6j&viIy!j)Pj{uqiRszt?K{q+*3rZjE`wi}oQ zV;_b%HXY1GDiq(7uP3%MPHQPqOZWazc6TkfljNh&y>OM^id;k~lIdQ(-Apr-pGdJY zx;I1lU$s=ZDuPd{f&nnB)bQHY|@Jd@>)yZk$8A69bRM|>Q<-cHdNPkw0X1G|> zC!vApljJ9zHwQFOPJ|Yp?+;F)Olx^gs6pjnJ9wf_9N6VKtRiJs#s;2WYZ*sW#SES~ymzWITf6SOl|Xv$;rh zwbHjTPU3#!7#66D@{Igl1c23{6htfZ{&&jm$NSGbU&i`gke}RyFHQ3jR8Rzh&>NIWCdBjrkHdj!TG$qT^B}?vuA#--ls@`@~r?%;N#@MInPKrz*iu!Q8&T}@~31*pQ@gsFTxed0T z$YWL(IVuK|OfJCyh&D94*XleVe;jU%cm?8tpbLVfv89p+%1s&``t(j9$uG|sH@gWO zV|7qE8L$jORgTEX=wHH2op-L}7DXmWw3+vZ6Q0LgVo}JIPec_o{) z{>bwFbLiRfKJi)M0ERW|ztA}2R2nSL)u7f$%=O%?^P!clH#qb7m`|li-RCv0@sX0Q z>N2cU?wRCo%95n=-9!_W)o8-9htkUz)_1>9RGuJNK`% z@9}tOU(eHwD3=Oldsm|QJ<`KZzsC$qV{~oE?}2W~Xbt0y6HhIXNIt6cVm?FMXd3wq zGOXhtok;MM_Am4Rri|D_6qdofsJtlCL0!gysuyCUwSrR{} zeS@{X7@A;2iF)R5rB_(*D0>aXH830r3mX6y&2z0it37howld+fT=~>GtFwJn`CHkK zZZ^Y8=Slmi`RpI8TNq95&D3L8~_m*z-g&wih2QN^L)ph*cQlcDH&;TRsfwaq|gFWC$`8d8_cCntnCvc;{HSjj1MgjRUYdo>135f&xgtaK6qEki#(o# zRjmPwO^9(4pA@V0UaOog$Th_$fR2`fm+i!DS^4>LKQXnDk)rWN6eosHvpNr6<)6`b z3<}?No>9@*bHep<;7Rn8r#-QbFLdL!SUylVfg`CzuR7~^1?dz}B;6mX>{ZC^Bv}he z&EJ69BIG+LKGF6;Ya&I?zfi?y)EYf_HX@O^B%BYSMJ$8*Ncxe-g+3ypW7w9Wxko8G zM~g$i?3SC{MpAV^+A4|Q49U&nDsc!}Q?bm;)uMN_G#`mSouXHbhgFUz&;X8c_?E&Zl3`?mb3fNeZ>|hYs`C z*3`3=EUOy?*)upKIuNo^J4eM}Xnw)9%(9O4r?OY%z9o2N^nH<9s|Ib#bL5lav5=O`Z3q9$ zZL@C)pF|xzNaOii`9p+LS*Q%->4Ez?6>I-rF;f5d=>C|Qp7w&!H|5)ToX?GS!j!cB zhWfX);C)JTENVU}coetcwk);aAWBDem`HGh`dZ(D4l0?#2qVZ=af7!f7QO&lb_A^x6NS#HMY{YJSx}WXEaOL~) z;k0s0_lpbpKIRLRjW|s|re_kmq+7H#YuYi;GVI%iVA&E&lqn&pgG(v&(G**X~Wl_V3DQj{OSKCORc|QyPNxgZ71z+t0X!< z9FUC&6UXrCSj7XR?os;=p&wYks6{w}rK`fFvH|hjU#kQr`ClK)ee&KqL_f4N4-yif zHHwifR{U8rC?OssEbskW%s8U>8lj%}GGQ6t`=5NT&OjtO(W@93(@98DBG@N^5^AVs z0c<;>v7*Oj8smf^&^*m6<7K|j_T>1Ggz%vezQeI)1kdTakIVQu=V!1ZOnsVE4kxc! z63@fRk@a)gm!)_7mEEiHws$NQZdZo5^376KF^yNROGVJ-(2w4&7uqV!Vfdm>9u6Lk zmnIdAE7YEmG+kv!JO!jFW<_IgY{uiq7>E|4G}nsVQN<@JzfSp@=65L0K==rZ<2DfI zaV0TH6Ei6b;HLnV|alfuA?~nNt$}1#B3qi2W zw@BlE8oRWG;LTVfxrD#s8w?knjJL;95+X@i>7UVa2k21@NBIj{X$LGEDEybhO0%-l zc)>^I=MenLK)dvuW64m6a&?|lDabY?+>|Y6_&9rp#c8QvSWkxAVlFjdU4eqa4J@CA z@^?Icj%l)9Q_tV!=PX}oT}+;~OIlvSCsn~2_HNafp$t>7?-g9i^J^yIV=)_90QZt{ zJO_-2bO6_ECG2ui1^<6lxlb_Bsazi9X!JKLVi28Hf0Nz`R=Kxkf6Lk{2Tzi`F(H9~ zV5{s=Bk(38IP}ahjGw9XMtVjq$a$Bmx=<`@;*(;nm#}~@lC?h>8FsHcH^$Cf`RKBOqVP_dHW3Mb?f{o z*LvKZ(mCV+oBdUiODQY44Xbkwt1N(6sspmVPgnEP;$Vo6Pz(bRd~@k3$(K&brqXO( zZ9SbNyR;jswWDhJNnckQ1i%HoXRP)f{9%Myti568^9dRX1 zG9%a!4BV9r)aC%3DkGGjsCSik26@Crhrp=P zfbv`+%t$7by~cB9yM|Z?_7J6iX|$>2OAF5_GROHDVJb<(Tjw*cvyE(h{xXhLSKLZ) zF{LnEWR5YYyn0?V$=8h4j8AzET@_A!R`ns)t#v-}J zDFv98_qb{o+rRW)ezo_s`~bGq=%g#=<+7PP4(!A1_jH~5e*NB3d!5VPww75{)~O^s z5n_}6UQ4rx^&L!crXVlx;RA;G8vKV2lkfQ`zSH&sa&m|yM$N63F%VOdH4mXbvVP>* zQHqDu8UyP}!k@NV2npiQmBiz8_BH+!FMDFUp|qaN9)kdpo#)w`6R*}Do3d-d0& z4T5QT5aYR}wg&Z#Isim7x@XuXau0yazhe7EWHH>EA@>6HIOS1_<}5gH4D}bR{5W$S zt2BuL3oU0s8<)!CXhC@#&smt3wA_ft&vF31jMw-`Y4-8*j)X(e({hImdp|@S5!NPp zFW$!OP(7sQM-xoLIc_=+R9*H;;jItywp92@HVo3}yb1H)j4-x-R zAHk|_9j|dxtUy9k)&rKbu${-iZGc(hLdo9aiJ%R7C!-aQL#;hpj9JdRk~y~-Dz`<+ zJ@liJThIpS!(}Q*6tNI^H;FjW4%LMaA0;}{do!Fk{tx=05vYA0k}a@!(ira*W-+|U zXQlLay61U_zePTYtZ7E~x}N8IsP{^FP4e4`_q6wd%5%3}59d5+PZFHPFcf zzuRg~*o(yL3vy=sELp{IV9(=Jh3G`=PLO-)=)0SncuaT()P}^Us65xif&EB+0NVjd zhABgk{3~59kISkKLzTePv+6Jq9P$*rG`k9X!?k=slw?;qU*a*rj-|QEg$#nTL4BwB zudpeq?8Bx-v^VjdSjpe_B);OrH#%6BU1MKggZnh^$w<-Fj*XSO&Gci~;3m17k;C(T zp1mwYSh;y$p4FaZW@w6rC_6^^1kI2ymVhNk>+E7W zWQY3Hzrpiko|G=UIjTeJCiIT$As(02JE|@W*Ono8)>Cm1O&0?XoIl55GFfkf@FgB{ zTkP)|!^0<-s~1T<6$}H3=!Dmk6`hcGQDyHT^KyWO!`bHICQtb?)MrW$p5s%w7g!gk z=f=AHTwBMXjRFw{q{d?$G`F((JSL9cOja=<5*%b~C79)#qw>+3nb&U(s230$8Wq4f zVZ`8p7^=!5W zIG|wOorbt5z2o$}v5w}k(!7nzHrLMme%2mcYOC;oMz<7xN10zJ-a1Z`iW)_7C`6Hc(k=ni;U1#X?R^E<9qYmSYKJ? zEm0hr{MbU3E3}xFhpD?XSpu$?E@f+sw_~rTBDwm+2rCghQ z$MhxRMa4|vdHcta!lqOk;#|V9jp89IzNJ^>>Ac};{}qj=T*1Oh&MM5X?$3V2Fi?IT zsRme7q)}OK6xZOfsP%LpmJKZ$gX0(cE#Pd?_Yfdjo+MGWoU@XBb{g5^9d+KBlzG%v zilPB+K2?21mW?!@?;{v~sCkMr1?3`^+=N6P8tDzE2mGAkA2_!oEE>sCJ#arm!QIJo zd|YB;+{9tBG8_)538ir9(>1?o+!;$WJaDXDvPE<(Wja$C3yBAV8n>p8%#nAE)F9Mp z+=X))R!kR2{Nh^Xvd9DRfUCl%#sl7Ty3jLxd6Grhc$Z|Y!U+|ZK*u0a#ab}(dJX~kI4h}BsiK%J%j4}eZ{Gj-s$ zRGgmdI(ae$*|;=bQv-gkWG8BoaLELMG5J#X54kh&S*lnL?6Xk&b(D<^oL9Axm5))V zY82V48q`)}M4Arg=~FxUn~YD6N1N?jvmdGZgvSX#68)G{O>*jPJQT_@tD~bAd`x`?(tZXWBns()Sut6tjTeLgBH&* zJY!!tuN%_IRa_6HJhuvMA}~%eP{|VHfLhTlo^ddr#+TiS`(_wWd`M1kaCAZf0zIsY zGUoF*f;Q0_A&VeWE#skE4k1Fd%2K!U8U+T zjZ43V$IRnW`o?@h36D$X%{XUy4Zby>2_Z$Tz9l&>s{3hV16K3X2(BixjZ1Re%xmV|N7R3e$)yUd5m&FmgaI>!CSK-rGG^bhg_jH!ZvDSrE|fp zGZ1+b0KG%Lj9Hgdar#)7(3lO@^_8o{$Rs7mb9k!1H|RODk83o;L%%s5qBm26|eXNEnGm-7}SHYLH5 zt?LAVLUwaIwdO#q%T-UGHbiLdg*n`oMeb3D^p#vvlKqw4^I|ovUiUy^6OAWZo!ek-d&?9EWJDF z-Xq_dd5zZ4gGfr#ebYuB19(v0M|MIe!Re(x_e{`*6Q1pBR%G5gR=zDV%6g*G8j7 znh)o}c!eZ}&|G!nwYf?{i-h+D9;BcPpG!e-w%u*UKjpI`494)(t>-aahO{m1N7X4pzDqv8pQP2&%H5Qct+5^{iIUTF+9;J)~B({pkMta4tY|3cpsTE2Nk9zZ|qJQ=gk<1vR}#&lr4HpBFzThC+F zaxsVa^L@uehAHQ6Mkmm^Inj+t<-)O#WVLrj(QHbUxJE*FqV*B-ind%-9xm-+Ln3Z8 zm82oK89|UFw>3GUa(VhA&LV9U!lC2a47YlcOh}}m4}3$<%2eN$XOMR4S@~1XkfNoY z2^Gt>e2243@r=f#Kg;mJm@K%ol3UcTyMi%)FJbJ}AKw$aipTPJ8U8Fk$?BsCkv=R- zWjOPC0P+KApEyJqmX*lZPthe}C1Nm$s&YCk$C4-0H-Kp)uD&HsIFk=3m zo;h}5_$r?oAHnXxP6v8cu5z>3W{ZtW96s1wcREAmLZZ%^)!qi)%|hQAl#j(Z34p7# z(p;qv;{_rkI%}i!9xVXT)?HrVuw;-C{*2~+taC28ACgIJEczHS3v&w(nMmL1luc`m zcR!VHK{i3L4a2m`IhUGo3<813j12{pp@yaIu{+E7hdhn|`N(2T)K9R=?_z#Lq5uqh zZOXgpe~>>F)OY#6Kv6&7Pog>6r;Hg)B;tVK?ORZm`&^k8p|t0^e_0CPa$+Hnp)lRlq} z7PHHZlfh-gkKpMDypi9k{58@v6gh;Mi^E=EWD)sxhrFZo8nU|IA7#2qYUi$Zl#h@LUORz1s!*)^Vc$p*uAxd-;vy;CE zY{@V}5kgwxA!9HkerQC5RB+Tr84~dDAI_9#TOQmlSKw(3+1Gfk=3K}s{#$oBm+AWz z%!0HHMmW+4-WOFu*5rAyvZXmrWQ5^jEi4#jWmR%Up<`LCk^3O~$*ZmtWJ;;ov*nz5bJP%qf!;FZ9O@u1k-2UBN+&64i>@`dbotzki^=^jG z9;FjtN1<%y#1L?~w9#Dqalh;0F)zh>ZkA!(Xw*~oPv0*8^!I>}6Uff=}s=-&Srk@;}MjzCk>r8GB>t&JvP=+Fu zmk_ZGl@y-EcL#j+2}JO!=WXT{niLLe;R6I4NnkN^u3$!hKxjxB_g^2d)#i9BbnnT)DVJ9X!KTj7!wT z-?%bxmE)p3_cUC&xXN(_)WStmpau0mXuxb%>1 zB;Rz6@i(pvTxGaio1kr6<+!5k@IEdBV3v)m9G7b|e21$Xmo4hqM&ZiBRf#Lv9?x*y z#pT)p-{1n!MHDU*uDiJOt?)jsa$GtGA$)M<;wr>dj?1+*p5rRTrF$9g<4VR=jLWeN zp5dy*pN^OluDiILJ7HY7%5b@MMjN<_ak;wy?zm3ia)(b` zh|9Js{>GJos|=TOH?)Hbi>s2?(H60LAiepitQ20*d5&bf4a( z`}7@sPZxa$^vZUr9cqK_)8F*I)(8D<{XP9%3m3IZ{m{R)aZwxejM}05^lSYd{hQj= zzN265eVQM=OV4R++C}50@93A>qnqIncSAYb~??M2J~`2gdt@ho;8g)Ms}QV$yIM`^0WSe0*!I zUC96;x`%g2DH$!qqliXRO2!EBPtDZ0%JD*+iS#;>J41*=edg^G8DOQ(B{dA0LInN2 zt8eidA#R-dEGToc5bN(x_Ae+BV#mufeRRi#xPJJ^+=6l7txrEXx@srv#9ywRPCp{V zuQlz0A-2sQl3A4-*?>B*v(sr;o;2}>IR~pA69e3AWsUf4lU{~#L3+q#<}$nqHLa1T1toz zo9}!y!I%U1xD;ie$xW_F&8&4rFXP&;~hIs^ge>OC?K!|_ea4shu(?R#c^&Buq&@#90qdI_( z=Uv@n*INkhifO5denNcPwB|Tt0LC@4<3YnBA@(nR=S0OWAzt6MD=uKK5Ocn(Gc5~n z{QSXtS7IC?r!MSFvT0B9Uf&=G^S`@$QjK!7XKN~1QVX6=# zr%kz$K3|COoh~F7EdzY6_BPb}5PMIicX~Z}qY#NMLH|~66XL?PPOskIBgBl>d4(o_ zA$AY!($Np&y0M_Qt9u0Smw)M#^x4qgV_Lpwm?uQPzG36MmSFq`Dtz)lCm|C8cbwQE z#9uL$4buRpsb^i9%*l-Rf&cfGby@W}=2aaV7B3`GpCEpyw7{t_L{m7vfU=hxNno`%RbT z5$<1uE{2Y+aUaZC>6)=E+YWSo!#4Ib`qlSZH^ajQ1-B{5U|jK_Vy%c?{nUio%xW1pG8NA$$tC-}YdZM=^c=CNcHlMt(72F$#F5P0|}DBq#65WbC8ugxQV8df;C$V-Sfn)d!7 z6Yq!M{Sy2}dtXb9c?dgJ!pQIG>0 zj>t33PS$GfP4#cG^L3AJ1Fw6p^qgIq58d8n`4xvlh}&$h@pZNh=%dhK#n$FRe0hFf zh$Hy&zYkykjCB2^;ZNcM44{t%aXm6e3UTpr`&=r>|7c^Y32Bjtb-15>Cvc$<^WS=M zrHtrh+E+dFA3$!s{qBWQ@X`4hwViB1H+5V0xf2DsHNmsfpxc1&@wjPUlTo_r;@?dj zdcl9U`D35^yFv_E_-Al1S{?y@|i^TV+Bf+bLICAh*Z5{Y7;fwd$mB9A$ zIl3!5^%Wt$A8+$*7VzZ0XKh1&v{SL(c(C3)?7!;N(zyt7B4*lmUCSVMa}J(*CmDFT za`06B;Ln8kX=PMvgS`-^x@VuU=?Z?E7j(tpHR!Ie;zKz;fXAb>(>6=NPfL!U^9LTM z{Bq3Q;Q-`EmsO6=g+g@P{^Je@;C1X8+iR=NgTK!0e(%W(s9)vSdc6_yxg_|HVfS^I z_l>-=Rh_{{1KM@MhIEm7achSZ`W^nu-O{&#$4g^-=`ikJzuL1rEE=)SwAq($CqVD| z1{v+A1Mkse8n41Q>a}Xsyy#OQ7H^qwBYT$+Zi5$`b%#8i_0NHyvOxE`2kj0xviyH5 zstfSme#xjJ^fUkBPv>X!5aNf>t8Ek8seR&xIW^ zfbWVWr)uSG5n{Vv*`#EQD|PjoYyE()Y3Eur_Hq;=wB7t)o!SfWQM*mvI=};Z>f~*V zb87p_v`o-XADe)|VkGb}x$jnwAR&?#?)~0%D*E;H%`C(3`}r^C2(L*GHU$H&Cm(hGEpZrZ-wn%qqz6Ke|M}&&X^@-m zMj6D3S)h}z+wOMy5bs}|_N@o_$mZ~>C$1Rp!vWI`4(;syIL-RMIfFICj zQ5o=m@0Z!x!51K3W=FKXRtxP1f6&v}6LeK^d7VG#u-BvAlcK1<$c#;?$F85z;cf>urodw-!&cWPBctdc&#P+ zJHNj9syD%hPQSi=dI4NK!8J|GkcV4kNKO6A-``5A8@<3mMkIk!RD@44p1pUW- zR1t{ZR{y0BvAGDn^ux{I!y$8*xLBlPaAu$P>=K)?R(*K{l9 zwbMKMN2gKH%L{HATp*Wf)SmQ7G3NJ9?H}HYf*cujZ*^Qsx)2Yp%-NO>diL~K(l37- z@K7(kX6{$ulcnt&=G7D;P<{E;^Ao4svx$?~ID$u-^{f_}JD7_Fa!Z9yo&kj)yH8l-?h9(xc<r?RW z#|zNVn1$dVB`fW%C z=rHB$jXk!02)V!VrAi~y_ox%SafpG`u1k{;-jxflH0y7!R) zjIZq7xVjl=|IN?5TZ92lYvO9n2ib`FrlT&&;M=B#7Cx20C*;By#y7B4 z%WE0A=zqqQ^i0s5``Mp9b3y-ZU+n#?62D$;J_<<#emlPq`=Whw=IlkHtRmdS$BJ_@T?-( zS-(!ppc-3iLoUruB)ZPI^`|io@+@oUltpUZt2c7Fee>9;%McixLDGP=PZOSm(#`+sdAZ9o@Ql^ET+E!Ao0vCr=Y1bJGP=7JjpQ^BSbXt~v1Y!4`!Fh4|yp zhr%E8+_2R7o!nM{L-$c5a$bR*aq~onG~loE#YX!xFrG(W%`!MoK>t3w9gO&$YWv@W zfayYPS+w(w%6S;a=AQyeGlb}UYGteBW$?ES?uyI!6#9Sq)dvMl5F2_}aXFv|@Od%w zs*dDUlJGYKLtjKztW3sl`Y6}P>`?e$ceX^f}|+a!1=e@T*$yXkP(7 z4h{ZorAx);_^K!8kHXX*er ztQ+|8Z5JUTc27;8@h0?*P1o8s_`Wi8_Qj$>uwx#ry?71uaQX6}FWf+%4Z7wIv27&8 zYwtKFCOd<#cb^;VjPJc`yw^5!2$G{ZydnIdk{zBLZ zZ!LLH4tm&maC}ZB_^7GJ!=L=lW1J6KUv|d$FX(cAi)jn{WNpxi7~rYqo};tw4}@NN zRR0Gzj3@cy*+cwBgKj=?SP%nvROZfYZG@b$ef@_<6kCP-o|e}Fblm-szboePH|8O{ zgy=bcIPllt#e@?!vj4}yz{fkf z_fG5UV7_HXM^@r@zwU4kBDz}J5csSOHugWmu7$>;)nPpLodbTaz8f&RAEB0DF&hOrs=Yf`5nHjrmU z-9xA3xj~-{w{;0*eA;FX5W?lsjUp!>A?A7?9dkECh+Tj7cF>0bjvd}8E;0hYKCWZz zKree&My3?)7vkEP{?{G+0LNFpE^{A)aqZoFJs=Y8}_ZY1QR{ei1?;P(mB zV?WDS1^Z`0nQy=@@Z;dsNp66X_o4NR0}f)m;}*ScM0@WXJ~|@cBJ7484@Lz7&#B!{ z9@`3dZGU*CTcRuQ+_TY-#=d}y|Ep=(fGdvKL=8yBxb(ie3$msIuFHRMqVuKfI#mt{ z&c=MEhcrzDz2-movS;=S@T+yPgVKTDZKh3?E`a~>IX+9TwFQ3v`e4_cQlCyz z-fs5m0{ijzxS9o!Yuk#J@6Lf-|NIrv+x;EjcjUHZI?(6f<_^mX0pH$N57u=+e-FpL z`*t$ZX;A>p=jFeKc%a?19{;73d&6!{^8Ydm--Ru7S#a$W%;W7f9`3-)Wru$c-Nx_D zCqMXl0Uj0GW(eI0{LUI0Tnc#H9Qt+%${>gi8JBFlK<_)ozxqi)XW+5P8{_VG2i}J} zhWP7+_&Klf8>RgqM@Kwre0nVCw3cC43E<Kzr5_k*pD=s?)KJ>hv@74yHcZ0>)w=$c6PdU#}_j;E{)?D_(}3d~-|qD~JhF28DvayV_!r+X^c3Qkk7nJ> zhWxnx+NY%_M!i7W6q_HNT7#mTUsbyQS-s0i(mEB?ZQ)dtVTnsvV^!euN z<WR&c^&#ht>2KJB4)=w>f7x{y=;n|2N}NFV^}l&C&?O1+p}!1u17Ab@*QV*i*e;F`08>N@KW=5I}aDkyJFwR70KZgm;K?dGK{Zif5D|Z=#d}4ebLu$4*09i z^sLmm82{pj0|OTW4t3lc+pU1TF+H)_427GByK|IHeG}lf&G~Uz?qu*^i~T3>qn|EU`u4xS z41Bkx?t?_|$+?3+|0mXyUDkhs^FAS#ooRK+`8?+BStB|n0(OP_bmIw()8Rn-C8fYa z;Oj4ERjz{{>bI*w(HSAe%)U_1>n!~CxX%aN$9Dx^%vtCR`PDk(@w>O%3$bqP$dkE% zQ|pS?hkLyNJ!^O8$5ilLtI2CxJ;?%Hz1^n;?U_ibSJJRvGsyi(8QomlfQ zzsX)OD6pvzpY_}OSpm~ik&`3*!L^;5Wx4_`3CVx^bjNrG+!>k%JU!ay`l1))*Qlv! z``kb`%eQpY8Ft`)!3(Kw;QNk;oclUMZtfYJ)i3iH?O(4g7=QYr@yUB2S7-LT>TwkC{I_3cv6QwuK#AF52YO1;1wh{ck~i z(1)nsb`{Y_aa41M(L($_vcq;G+S}-U^}ozX;Pb3OH4RhAe-Fs?1O4rpy5(2tWGy1kxJw7jr6dTriGrY-_*l z3OpU(><|(2DdgbKp1pm*hx=A7=(-BuJsOwQArbxmHa;Xe4fCiN7}GclaGK%sL01tE zJ3HU-z76^x`E`?R+39$1^XDC$-^Y6g+Aa(Jko>KEyNiIgoK~~fmV&NJT7BR=V>|4n zCtWw>5j~XMADZC+yWmBaSM6cH_#W-o*v1EPYsAKG6@y`yf7Lc6jOeoF&rb~C<86PK zLheJ3Jen#dH~>DUmKHD1KSg?SqS5Cx{M2&?{tl}F`z__kMLpnO;pjBteq-Rh`=>LF z=%?A-lZAPJU)Qg`EOtZtzkYROcm~=vML1;M9t(NU!K3L3$k~{zI(5od!M=HY;9GZ3 zK%QT}e%%&u_~qunCQkT$*gs;6Q%{oH#f5hPmq(LrCh5@MZ@SOkm_hyOZr=1GxmeP5 zkqhXoSJL2;8NgS-{KcsmDbUk(ygS|B3Hda$P?rk&k2|{ZLMGaKaBISA9^k8&_B0#d zFbMIIk4FXA4TGH9dux%yaOjO=E-j4sU3Pi8on0j2JnrusbK+n}xaVEEI~n@--kb-S zX;>$^GVdoR;Blwl=Qp#Q(a*Yp%U1P$>k9TLu0ss{hO8j6RrW&Tlj80WLG$CRWVo z2mFTp+v`5s`?T4;J$8_f%RhbLw0#unV4m3j(2xK)Or5*Ne;VZK=sRu8@Vol2SI#(s z&Ii|-P@>NTJRG`iNZW+(V%mFFf=_ooc{nEp@+JFxZlkD<;PYGJP7dVBiCg23-*$(c z>@?)foYx@_!fL(hI0F2%Vfl~xK*+B@Gd5n^54!xN>xLQVzjM@Aj|yhPj+<7u-4oF7 z#<6wQ-d_p1^!kWYf&P}cbWQd63jX%5zkFbO7V_&q-z_CAV5dOdWVM97xN}413Bd90 z{BO_azXQBSoR7I14tbe2&NBt_FgvK-2&b-qL(fL>Pev2{G@X$KJRV6KdegK4`sDss zivzcVKkJFcnTH`44i9-d?>Ok~@PDc9F97ZapU!k{S|`)OO>{O*f74HA3mT&RBforS zv`3s~(jWik1HK~sR%9aDub6DeEC_`ixp2dZa{N{dGFAj8LqCj;c|8jJ)8o3{aNh}X zA^u@9lM--*Us( z3398=n$7QJ04|5tL~aZAfWAx={^fe~mlrZZ7{GVOn$|atgx}mN_t(k*(BUU1_iUx{ zT`s(>n+Uj+`Yg)>J$a4p^_C;X7qQ|)H` zO!Hv3F8;Ao3FK~v)1zW@_rp#)w6=~%5$waWTWZ}t1AFVUJHNPIz4^G(!L9A1*q*0CxF%-xxhU1KfZ1 zoKjo>di?&Zlj#Kf-0vr?PC1Ej@95!VhjIG0={xoQS=fa`>vjmn{OgGa|M@y{c;M{S~83FiWea0pi<8<2maG=i$vU9wet;&NO zeB*u_%6ocbN|{Z$OS9YV%E|2tJ#q1;FALDovMDc&GtNO=V%l5%qX772)6f5A3;ZAa z^}7#jN+3r*d(aUFXbSz!wAy8Tpg*1LJ+?xAS48V4R6y@s?pt)!e;Mreu{Q38jfl^9 z)P53!d15_w*fq?rYnyhflUvAmX?bc}^uKHD3}buXbM6bLDd%V5aF?kkyg{cQZ(1G( zdL7zqK-WUh*Tp|3Z8ZYkZPt8SP>TD33o4rDB_eM7^};>L==b!PnRD#k$2bl|%*oCG z{a@|0I(;tprN9#jZ;9Exq;W)LjGB-KrtaJA zUXl3OI zJ9jkMM%u-TDKG5I`L^F8`CH7t@b|+LL|--+Pp9?8zl-klNDV?fzW2```A-6USJpge zgZ3INI{$U@LE!cMv2!yI!B1U!tF?O#$P+QFoxuU)`LO7!dm8BA@%_gcfY;f_Wxu+v z0lt3LW#{F>J__9TYgi%l+Syx8^MU8e2mbdAjS#P!Q~Fnq3*u|HPET-ihyPuXTI}aZ z@yG_fe8xepMoy2<3`ZQ?xlIkn4=|3gi~lm9-EWp$cdXcq_}8^9A7q(8-%m!gP3#H% zoN#MkKH&0O;s+aZFppVxYCGs60H3rVPXqAx`0^}^8XK$xw zCbWC#nC-b>z@>iv_wRU2hW}SP?N7U9uurEK)py3Y=N&v7VgC~7+^~9oS$(ooR@L`u z34M2DX^0Q_DD~pK*|yz)*PaWe-UgohrWo%zLr(k_u=9r#>G<88<`D+{y4}zD>?#^h zmoDQ9@-WYBT^HPjygaa`o?SV{_5ESb>s0sCvwToZV<)Lc4g`0HUfF%4QSor(JN>c0 zZZ^i*(01j^b_+4ClZ{W+1AQGiFh0ivczo0stLq;@zjwaW+YWR#?|#djMcctg1>1a5 zc7c9=iSFfp7<2g*6KHfSkH~S3uw_&MAzzdLjI0H|F}DB2j*YUHn?yf z+M5~tq0b4#@4uh4JPmy5VmNTw7Vv}e&d=z*Xmpak#}1vp>1kLEd%JUMcL&hHmKntj@w%v~ z-!jjxCg>qJ@H>xMuqT3FXqg3hUHtj+w9?lRNASP&wCeEj|H>_n0mw`Z2#9t3&+Xsm<(Td@1*53Z99y7b=Y z`KArvb!bWC8QX0bPoPaBpD#$xUo9wX3O%-S_;e@qfA;gpEv_$1z0)TjaId_)`P(wU z-7W1KuRQ3xt&6&k(r*AiJvlhR7Vyr%I$H8Y@WX@ge-`$IUa9C1P#y;Uk39CJX(jOZ zvqNPC=6~t$k8TUl#V+d{7+pZ(s=4LGRoQI1vdyC*Z5i<@jyu z^Y!a?Ghom5@p5~z0RH9Te{^|kzz1*lPc^Os{iJW+>koajdFT6m0*S7@YMyf*06p>O z?55OFm`BYmE1l*OJ&&%L3Orux+xHvyPar>Ay?V}=E7!l?Dn1VR`tRPORND&c00-Ov zzm;uspA@#oZ@X{GqTE1_>r4@qy#ROrnP+2q!(IwoStkem4fhXQ9YFMOC7`Cu0*ViO65+vLqm(9DOF*o6$HU7K~Zb0 z*v;CiS;7{<^ZuM8x9{_PJ^#S-dOdxK_ciC-=l*@}bIyHV*L5Gfd|(?KIC#;rO8Q6i z%#oAxxxb$K;IYp(aM@_Q>q&Wb+$P`MedD+vKHmS76!$6Xu@}_R&#(FV#9b5IC*S|) zv6?QP7eAB~{8SjF-d{Om=g1A**CYQPsd=7q|5(0j5#wXYt=pd~o5A;ES71#S{WN{u zrmU`W7;lH|xowc^d}oXt8REX~**g2oIZ5jIuQ{PI(w}(iz2CGk{w@15az%oA8-3m9 z6*-k|dv4C9eBVB_{p$@}KYHxFzBuhU?!?kZi*IE<;L7X2h^^v&X?W}Fpl`F*W#O6*n5AB?^@!8r72G%FR@oB6K!u_-ae*$r9ItfnH`|Enec4F8sT zdh+Ih{wmhDo?6)5b-CWhUM*{F;ruw3lc~2CpBZyl?@HcJTL1P@5#_$*n38+bu=&yF z_pN`Edj06^(T&4-AB)|!vHJktGqOIO6*-*ra~J;9dIH}o$KBq&m2uKJpnYs1NGB?{7Y}t)2}-6zIwTw_W5ScH^+Aq@3tGie>sMRrbD#Ie&K_LM{o4NK15xgi>pwg29Qk2CV)3`7tt?e2cqCQG<|8#6qDfdtI&7W1xPZbBnLjiCvERfUdDP=w z+rJvw@e1#`KixCN__@#bSpT1X`tkK&Z+WcaB>K_0zMER8w@*%+yRmW+&-co$6E^&j z`)=c$+j}17zASq8hC$kM`Fgb_-xBWVMpES>pw~N=l z+J7SRH=mAw+mw^;?KRnL^J)KK_dJ-qh<4iY#V2``|J_%{hRfSnS1!8m3q!rnUz_^Q zH;n!m@D-Ho&-%w6-@MXG|Lweb(N&d&^oMn0w+DjEOEi{#*-t&*wxMjW`%?Pp+~%&* zIPZ5?zWY==<%(`U>cr%m^y6c{-#@XF`&_tS!*u4QC|?`*>xbuIl@ei`-!bOKDA$jg z`@2Ok#=isB?*D%FGxX=PUdT>A$9VesVH+E}Y5!x!_7;q!znZOmMWe`%+4g!vF3+E- zU6Z@%2VGOctC}cpV(G>M^QyVORzESjoBmeO^p{NQ4TRq~thBrteUqcszus~e^|<)v z8NGLN{8x95=pfxmrQglo_7LwmIqR?Pe3E$Y>D)WPIQh=xaev5lcuzE^z8c+`bvkom zI!XF3C4SmoOnr=5xBcP40MGSHul(mW+H2JV?|qO>`QCjouO}X6o?y|U9qkuzAJoJX zCdxe2D^EApH_!Q`&W7N4i$%* z!;Xxg{>Vd^@@Y**HciqM3gqy)xA^rs8v)0)B zds4rgOT)^2h>P)1-{6`z);CdvcGK7_eIU&R1jZ5nEXHmMr@0o*v+9{0h0@)Ok*4NC zWOF3l)C~Po_e6?%(zl8B@|3QG{FrHOz))XWd-Zc)Xs$xwtp&(8rsimpGq@kFBmGj2t>8D*w?Hz;dCKpb$#(*>Z#JjNkD1Hgah#(+ zN{3wfmzw)eL)|OMa_WutiJwflo~C>up8ND|Uj_9*K4bJB&GE=rm~=E#P~W2Rzoc*Z z0E|CDyimIPas7Uca8Pr+kuxdhB;q}rcE1Up!*yAlhx#^8hpB@Y?KGb_NiVv9e39qi zLZ0_a$VWBx)kZx)=>lY^bJU@T?MH#-9w2w!c&jhCqcFY zX{folz~!V#A9LsVC|{H`r2{MbS?Ng2_FFpXF{p2_M3i~>V>!1By|ZGDLwzgB=c;tX z6OH79xWm`dpVpC%xC`-IN8VJAP|utI_p!cd6HsH9?*`s8y2%TaE~xn<Ax!p5A}`~hkExs5u?!k87k8^De$D!tGqtJg4=|Zg$r=WBoGB6e- zNz#_?rR+zwhL)r*^-UF`?X{K|htfrrkM))$=b^q4wXUf-V)<6mTu2b=*_(vbDe_4i z#mA8ks5yoxl+KdIH0gF}eq6d%DJY#M%^T|5Hq%c3p!}Mjm(Ep`J|o@Z;2OrcnZyV6 z?w5Lm`hS%4`P1B82x{&>2DPT?PmtbX;vw&v15+7-{0z~jq%#sHPtuW#t*8C>=lGN4 zb2RyWiu(8q#~FjP<|_Mc>0%~`TRMV%((awj^-wk*@;#$>%upxwo=+OjQqNt)^Bna> zobtP@IT>G)ag(tmwu!i|;cqix-Xi|zDKFQhxJKV*=0*DJ1=ItS?Lhh^(hqRHoA#%_ zX}(zB@Y)lzcR%W*hqwqUA5PLWj&RRu&Mie7XpKhSFZ#9!l9twvBT#GY;di+1Wa5K* zH%V>bx(d>TT2o8EM?IEt?fZ<+^Y{&=OJ+Wx-ge`5g2ccD4pjRlrCKObB=RAN1@gZBVSOj2XI}6cqnJ$ORw(b>rJ*`@~y2k zOaFhUkMV?uTH}s>$Mv*FbkJ~;|9;^5JBj~C`u+Xn(>O+RfN{q$(s_{YdD&D2cXEtu zU}8`<2=dJ>-MBbDk)(?v-$>D&iEkIj#1-E#$4GZmepIC!Vn#Yf?}pm{O*UZhQI3%= zy!^08_b3CkrX;_f`W6W6M!b)3{qByD?wowsR~|w+_H>NC%@cb;?hWlhk|^LjlrBqf z4EZP_pHMnh2`F2($XN14cKNHZ z`VMo9Y%1fhk^Y@J!ZBK#)P6FWlTSj;)y0pZT$ghlOc9@dhGV3Yo`!KM%vVagok9L# zh58Sa&XV?_lMSN$he)?oemkU#lQ@?6U?k`mt$E0Ai)@NQvm6tk{gbn)PvxVG{4$Pc zKjrvQq-%jaT#oa^U0Mr#e)6IV89(UTmbHOM%Am}NUBf6Q9rPMktL za-IB8N*69Y zrQkW-ulo@WO4mYrH6{I|7p9&&hzG{n2@hqPmVvUp3eBfKQ{M@wZ&~?-l`Oir0`1<&=+Vtr4UzgmJ8ymIY#ZWG zx`8H2KL~Oh%GN=9EK3*9EMvS{&Gk^WctMz1MmVT%Q~4N?&6@n7=91Mklr4b#4{HuM z2zyT=JnZ+A&ql@<>REeKNtY%HrE4FD8Oo7_vYAf9nr`a(TFz5X@3@hn)ION#v(USZpxQ| z`WE)z!uU%6OWx|Z@5JHd(l0V8Dhk7^Co{U;c z4&Ot1hm#JJEu;2+)3if$fR@G24(YNo}j$kui9TlbDZ)i zE1fj$$D=h0?c=^}J@xrKx+{O;yHeUs~6A)kkm-mB!}9LfcyOO}FKBQ`zs zrz)<4n%j%L#`EX5)cfm<16h>k4bD@K+A~VFCFV`?KaAf{bMJB3%6KZjMS7PD;#|9+)EuPmeV*s@2oGg*n1HgOG#`+e zAo0V2B;|tAnKfJKrx$Y^%JwJ@rHh_{J2r9rL&j5nC!oGv{eR^*_gxgqwpxCbDsJWY zN3`opjJL47gl7P~I^?gA&`lsrvpJlWr7=+S^ z4MW-3n|-setD}7TWf{%+%0IJolY&q>ZQ2hdvYhk^$;TSvg<3N;?0=^z^YY7$!gRNmf0I_@Hzd5>RXA87Ny-`9ziORT#=9C)! zj?E=~+A{%bD3^R2N@q*H(PZ-#ggeM*7-}so3bob}hgwrhL+RSe2eNF4?*XW} z{}hyNvHT#*mxlKClW&v|l#Y264wMof)Hk+#s7lv31GjA^{*%d1Gvz#mbZ;hJC>v$@ zrqx<*97-o34U3i$E|g`oCK!gY0Z70CKk3e+-cO~TpmYn(sl*R`Fm*5SKtr0KKX#MoFKP8}S8#7RAh>6pbWKz?9=eien)+z)9e-Q++e^+Ng0Z;5vk^#`>kojikneI(^O zlXBDVV^F&A38=XY`G~H3iF9CpH}O|dFYAa8HoieTknU$9P_|AnC|$t>)EaY2$JO7U zbaQ=YQSbM2J=B_32ue390_6)J2IXTV0cG=;((g;S9?BNLcQ*M|yTKOPKMAFCFTbC% zT@IX+Wn>dk0xO%CBY;}t2*bo()C)|(R#;3wYJ(N91Io6s2g*mv4k(@9f-vQyK1-l% zXChF#-)26?qvQvcEa6o_zZu7BVfIYsT%dfIL>5ra4NMCx6n z`+nBB+%Hd|!*gDikq(c45#^7u_5-COv!;gjos2F!jKaIlC!dVp??bJ@ExLgE=KbL{ zC|k+UV#e`_lo!gEPZG)}h_9CV`kB(P2=QCGsS%O-ib3i2Bw!u=FmDOhQGXA@Jld`5 zBF2Y8bfsXNe*QL;4$k-p`FMbIU{4!sMo??M+8uw$179{#-$3~WIi;TTdYJ2kvO)Y9 z%BI)%JMNdM=v+bhMyY|ab=eH1yYnG5aM~3-e}(eSVcd&A*-9s%bc3`{vuqH8zbE{OoQGQDOu#ry zLD`sRpnL-O{y@3!1N_K>T5hIW9mWlFMNegN%MtzWOG{y*>EM{v+`^tij$yxoE$Gpln9Zx|#U*WepoP zazCDa3++?GEed5@R}|wu;C@KKrZ_eRxAL40F;@wtTc`bkv<81X4AIWJFQ=Y%rvF3v z`tY?d&d@J{u#Ryn0;?E@5+eCZLD^>dZs&d?zaiK(hH}7EC*h!cr=_55ATqG}X~O@B z_9VR^ly98~Oi=G}XeQ7Ou>K|5;SSn$2Ju4q(2Bqkj%yD9*^p_GLOP}qD4W_Cl@gEeYD>q#w92p6~p4>`(eiEmE4!VXH5ah z_V48T=@0GH7c7`ad{Dm10;}kU%ZLxkHa-faJ06F!y-Pv)zA|mJYZ>W7`KpS-7TPTV zWjmq0?&X`o{{Z<}LcZX@m4t`#Ef9tQ%AbH8Tvz-c?Q;{yp?srsLiuFzt)_l9a~+h< zh4yl)7{S^Al+IQfrs$8u+o|6>2?u2hAAg8`lR!5C%C@%jVeYe8jAKx`wtKIE^w+5{ z9GCAP@;{Dl0HwS7H0_~8cT+Eq5D%`MSP`cTsgYc)*U7>81K7@rF{t;GFuJhLOp7SWWbx$&m&<|dM@*UR? z!@H2qQ}nZH%CUiVcnzJRr>XDptQl{lf01qtlx;~Zl+Dy2^qoQZpP~JNln=^Ad5=!| z%VRu0o~8ev!1Noequd`qM>?c8EJ=A*W23l<_|9fc2Ig{Jl#iw~ET?`%+4zd`#WZ0v z<>a{72Y-NrQFPwAh?n}G@*43_e=#T@c8|l7mw8^lPJC}rPf$L-3f~}HgKYZ9H}}<1 zP`*7LO>sU>eZdg<*IwUc&rl9nOFg^?Wm~QNvdb7hcYBj|Cw~{d#eH)Y%d}8BUH^bx zckq1eB^}!FTv)t-^kIhUwMVsV-osG7R$8HS?0$sN6jOfh@cu+TUx%_;Jmp>LnR;!6 zT6@zT7}Axz3kEl#TPxne``P=P_wk+pWxKUjzhUbK)D!*Z+O6cDd{6t3a;hC+koueR z5zp5G-Wy;Y_4W$PU5RdWn*PM^<)09ad}}Xo`3k!R%E#2wzf&ICz2qMpr~kyDbdI$j zdJW}$0m`=T$bV5!l=H&RDIeu{0m>F?%y#a#+ZeB)bhR5`^DLHIVc}D}r+z^_@xIy! zrF*#@Hu`zr$9WRrk(EOzJSsx8~-KsdlLN~MyRKqzk$mbUv|(= z^vjoE&m8W@Z;7Ah(;45P(p1KGA!kqp?tV( zgVL4O{tdE`YK9HOyQ7SFmeKyO|75PA@5n|k8%j5MIFzn@4wQ|s_C+n8LOAL~I>5uB ze98GqbHz+#DBbh`ahA>}ER@g75-1;8rLcMe<%QBQo&(FS<~gO~P zOW$~yJVDv)CB(}q3zScV6*m$$_hUqTHjj3M^1W9NWm}-JP(E3UpmbaVus2D*^t+k# zp>(q&P&P*LcPE>eCMcVgX6XM7?F{9!s13^JQUVr6>65T?Gk^Nc_^Njp*+6%3kIMGA z2X1j_7bxBJUML&9tuPO!VdNsx)Nk5W_jUt8vhOB;De4!>C!@xUrsp{i+u;C|?(YsL zozFqIg*uZSkaYR8VFPguhi#>_ACzwuACwQXTqqsPJeYGEaluN)Sv}KbgHfR4gfE2I zv|kZa-2|X~ofgA-?q8Kjy56NwzTjf|O}s1aC#}VBHFZJ%Z-rZL<{p9BuTlnBxrus& zL~FWWt)F`f`su4ZP&Vu-C?9em`ap~&!_iz$_z_s2dzcOr&K*m zii{buA+CR%`YR&-C+K^`pN8_Cs69pM#*qQW{L(J!3(BTF2g>J(56Z`nAIjHE0hDi~ zA}Ak=0VrRfB~U&NN?{5HVdg^O<)3^5`3vtvo;WFc_8=9a zac=l{Z=4HJ2N8Zt_qPsiSVCT)e3LXlAL&P70rjhKP(Cs>4yFm;tlw3%3#`17drQ29 zI*0Og+Y03aHx4tT)283o(>}15_E`(-!?Y8WE^h~HzMC+xXgy_yvWZJV>8f|ZmOZIw zC|?pi(AABG^3~c4rIWn{%D358STLP)uyX?U0hF$BKkVL@z7I>DByUhYHU^-4aPNS! zg&u_RiD9ylDUY6yHDjndC>`V+D4$jCbCUAp@>@0lc~CYn`OtqPe{eYED1Zf%C=-+~ zf+E=3K|4cJM!$!$@h^cs($;fUzN3OrzMaaTd}Hc)E8k4zP`(dCP`a%ZP(D;Ep?m~Z zLHXDTL)lta!xa6d2G(&e)xw$r+8@gHxDNI)_GxY@l1H0B*+NI5Y>XOV_GH>0%GY-@ ztfyQtDBH&torf!+Y+G8PY#`#WpqKWCy>!lY*g^VhVTg81K-oxlz%4DbKkTL*I$<{B zaT3bLxl7FD9E{GO{h@T^Q&6_|y>Ni4+yb|D@CRl4mxj_M?}JI&tsf?+hi$O-651bb z<$f4|@{zX#%JaR|!y zUj>v8qe>XdC$CVxzQRy8Vb!pX{#OHKTV4z0`yv9%j^-ScZgxGCjYtEO@6sqttfKv) zd>b{vF#W9==FcK6C|j)-Sa&h)4`s{M3S|o$heh&Y^st>Ah7xg!JCJf^_xXD&JOl zP`1kXP`;x4P&QEoP&)sGP&URza9b96g|f9MhO(*9dvD(%C^s) zYnEPm?j_!dG5Xk-r=xvpP@e%Gft9N{)2|7dJN119X=a?odkH*Y-D9o-Vg*?+u zYd4cic@A}DpD9Bg@5;NI8Y}N%nytL2N!akY?su*ielL^FbJ3IcHicFmW6F`aWhi5w z`~KpE*~fhc+1EWj)_tGgwSX|=+;;;{&Uc^lUYLE|XZB%knEl-6so$0Ncb|=37{B|R z^R6B5KC`@ZC%DfUFAReOV~1DI1@3t7#dCl=cB^gt&LnqS^{ze89SglU3*B+;05{A* z?wB;ml@E5ulLKA(5N|ASfI8o@{!N$q8vqF*}fXbR!2$;V{M#SDxzb zcjO(PW|}xowyQ-HC8S$E08C-@f>NA_V`gI zW91p{eo9{6N=?~_q5K?e>X5zok1=h?Ui`oYMg~;CVS?-=po;=$$ zaoo#)ndw0G;yKRrA$##3Z*oVu(-@TZ1XF>`BMo_uX|nQM(`n@s-Tj@s_mIYV)l zn~;@HGErnN{*z6@9zVsTk-hSUO!nxZF!M|?vKQy6rW)DH=dVqR4O4;7qoMlwjTt~b z)XnEFE;Q}-c(vJLkDqIDb{`6Jp1a4D z7ylv?;kcK#8WTsJ;--7PNm=;scx8BQ;mF-D_>~h$X!J4AkE7D@_pD3*TTO$X;4kx%OtB{CktKVg8^^_kM1RsQXUs z^`&L5p14;JSDPln_}%MG8D_`sG3FaNY&XkN98Z)mq)yQ6cnoJ9_mxmin z7qVC0Kbj1(ckPYFpEvZ}X*MNRzR9((Bir9ev64(Ic8Q^`Bry7GB5mXrrRE0 zZqin6F*~e$yYcNal!G)|-4QKWU1s{FEuT@&;3F<)=-9l{cCeD?ek_TDjA7S@~JB)ymJA0W1I6 zkOfC;- zFAuMpA}jZpGAqAks;vCFsk8DMrrF9V(`Mzrm`*Fd>Dp3x@xNuZ+2g$?n{NZ}_}eDm z%I}zBE5B>Xt-QrlTlqcHVCDBsiI%J;w zu_?93|7I$z{E0i?=7s4qjrRDbrq#-SHyu{~hv~6$zv;8`Kh2<(KQp=9I9{CpGDTM2 zX3DIL>tZW!cjw)`@L#xd>Yki&ZL2)_-=@=s|I+kY`75){$^#~Q!ce+jn|v#OV~VZ( zA5(7S9qt^o7yq}W!5;t4v{?Cjv)0OkrpwAdn5|a+(F|DmCzHc~&tr&j)i{1DJ5I5c zvz$3r-pQ%5a<(HKPVd^CoklC~;z*a%J3h==YvtjNbgjJOBOI+cc=AZ6-^!z$K`ZAt zK3*EVFryvKhk5d@PKlLwbIPskbHY~M-Kn$k9!`^$_jFpVoa@L|%!_j`r_0KFJ6o(g z#?gF?7lzd~UY&TKOQS!pa9bHC8^vX|QsU6SMN6 zPMeh{JF-#n(wgG*SUKRNtvuBku<|r#I3E{Y_~}l*m5ZH#l@D{I&+Uad+^Mwk5l*d@ zOPr{ck91nBe3a8}wDMf1*UBe4{m9;Z{VPYl1w6Uj$+PlF zPQc11J9DgjiW9bS$f>vTJSX26=Y&N8CY zgNmbZltEla9T!}NQO0e?<>$|d;y8egI*xcFV9E z=Mt6PK=jJ%B;9%k5$M&=mGqk1h(NFVgrqk-NCbM*d6M3;n+WvQw@Z5aBSfHgULfh+ zeMF$YoRIWaw-bThcaNmMewYaK!Nrn3ypag>x6_h7dM6R+<6n{ViN}aQf5-O+ed;@s zK648Z=(8V|bZ?Ca^tsnc`p3@`fxbkNzRbFyuk4ca)r*Kg-)NQeP0laq+Yd@#`f$?VA*V&To@+;f)l5F1}OJC67`B>SP^I_Z^a!mM8)(Un}VuZ=?wH%!eiQ zt)K|Bg6{)b#rFVRae<_DoIcQ%cS+jtLyABH3nXpiG=MhWF6r7&Qv}-fh@|ISL=ouv zQAvZJqzF`gOww>CMW7v3NxMEu5ok|I(%u^=0*$|4Qgt3hpxSmx`#wk!=pf63p1)Dj z3*JHzXnKyMn};a^z4!}~UfMn(yg~q1iI}jl3v5Ipx3=z(i^y} zL2tTD(p$JpL2v!Jq<3(6gWmaeN$>tKMW8!-CB2ui0J`fwNgp_hBG8ArCH)O!2lSEm zO8VG?6oEdmSkfmsFQCJBNc!{x6oLMJv7~>vjv~;#{9Hhv`-~)pbOid+5=mcvE=8cP zyh76b90%y@7fJfp8bzRQ{jDT^r6bVy)=PTeR*FDByhqZ1v{3~5&ub<9*DEOk{e&?C z`k!`5kL;u9r}tCzvtCKR;JkuRceH`v8?dz4u94KA%dUXKa)7%#TtD)W`P&t=KJT)n}*#;`dL` zx|^s3x{_r<8y=A~@L4K>Hu3#HTMkIt%4q~$w@}h^H&Y381D6Bn#s?$~wNVLFxlz*2 z4^s&=@`$9-ZYqJs{zTHmJyZf!e=ce2QYwM=Pe?j=CzU|Y|DmK8a-KlbT!x^VZ!lLt&^}3j`avpzUiCkcZo7y|pxZf}px3@a((6A@CD0o=y`VRrA?Yu+ zQ3>?67fX5v;|282A4z)8Vk&{|yiU@4Ie(zLxLiOVI7!ln)=&xbH#JEgc@LF9AA3yF zCpb@_PjcL#PjUJ|cfViK-#<(x&^?z)y6<)>fj<8YNne~pCD1={-a-G&kLs zadzU!EBh^uBZ)L1Rvq5a8ONU%l&b4#IOS2zH+*Q9l>~9&hL?w}1c(b0zOz!>yo(V%JTN{!s=?_ojr@*krDB(ieeFt_ zM+g~t_H|N9-b+f@ zcN?yZR%-E!p^T2lG_lE`fm&6+OUq=;Y5b&C+PenZCw0?8vtqFCsrCF^*-~yO+t^BF zV0>a~qJOA1vag~a*)XiH7h+NYdt;kV`#LEl?=B_myO*bGdUf;orh|L8kB`cDT~1QclnKgL${L=L-c+fr+P-rI=V=eDuZ-GbI`S*{1>4h) zVu+<}mLn^~){c*>SDGWXI7V_qW$z}olME_t%UzBmxo&KvwyLtDJT+RgUvt(-32`*n z@QaqNnG)h?T3Ys#TsL_wS8>B@*>nz!kBrrH1)Rn=@vWQWw2oH~ic*(lm|z*%{v8K4 z)y5|@JgrMtMis1(Rqrrl6YNwSJRV)_mX2mSI!-$KbS=tSet@Hg@|Bqu?oEfrJZq)} z)zg&)<}2FIg+o_O?VXT!T+eWgO>%mObHA0lDnomus#WX>uA?C^?PRZ`H%?}HU|s!8 zU7bhv-uQN#rY0t;mB~qzAivwf56TXacU0k(+NN50XphJgSAcl@*&#{I@{UUF;QGqG zihW0p(d8$!){SkMOs29@bGQkOE2hfTVVDIcbq+U8BT*8Sn$XbPghooyRcbafA;0>- zNNufjwVhNY_(`p?TJ>P6S|T8!$5y9OMG3%(nA|l!I_%suJvc3nYo;nwmGzY|U)>Jz zHDO~{SF7XItEXxcQ#HJ`F-(+6Oav$55CEHiX>@MwveR_<89Z;QGB$M3*K>lBy5-SP zZb$*y<&m*TSJ@A9w1;+8P#?$d1gx9NZCa46yRkAcUaf7dj*so!T%O#sv9iP5IX%qP z=9G;OZ5ZFj&OrPoZj>#xm}_gS5gwu_`gLPF#)To4Nlgp!HCOTtJ*Ajw%L|O*g>bAy zk}EZaMn?vx#^6ebdYNutGisvd$;$)nAkLk#x{QKDfWLv*! zqC6y=RosHL(t=!FZuxrmf+uL0Mt;Z0*s$4pl)YW&xQna0V)zNiAQZ$|V)zNf9vNUx)>uc(e6it9w2Rn*(K#E>^1?a?(E|L|O zT^st**rDk$q|}$mXi`K{;u4imTyHvYxItOETym!V^!1(Kv^e~_(v8FqPsWm7@0nQA z!~Iy?y3y8mLz0@@n3OLsyW&R6=6V{{@mgi5hE^#2cT33)@buW6qsa_!EyhS9hAfec zxWq#?4=FXW6q&Tx(h7l76SLEF@ZO2+R4c=A9VHzM)LmY1S{y&siyf!EvG0{#VZRt8Isf#5a?%L&M6<8 zYc$YcRDCS)Y>~JG%ae^Cy%H4Hjug*>{GVYk? zNefKtl2<8-d7ku|k?N!uxnX+p(%VQR-7tN5^ds0sh^&fde;wVCw|$O zv?I4TjwD_dVbRPpw^^R79VZ0+w2Ca)Mv_a~;mJ7V;MKuNJY4R#5JOz?l^FHXJlw`e zs4GW;A&%Zgt)MTB%xI;&ud=c_QXAp!)=g~0N5;p{c_TD31C9O&_bCpPcUG<~S4ZI4 zD$NwO(ZLXj@PN0BRV!@bxPZm&()cM_8&h>+uF+sGpX~trhRK0)Z5Q5kai27PLQ9M! z3Qyl8PpBmWjNT4@7^Y#V@8xqX4ZG1GQkK$c5PWVPRjOG}DKpUANewaZb5cd%Fp_fo z`ov`=1vST#6fF?xilCp6qZZ~yBv;ztl)$9Ed9ihQnYvQG@Zcx4L%EOgIhb9`S<|Gd2k@$SU)x zLCoan#Y;3Y$pAqQig<7`d6~gFfK7umv*souKEv0NJ4Oyz=1g_&Y#EE$QqF0nI&r!o zmUN;as$v%6CP>P|9Bzbf&RLbdm2-=fV56t)xTg^ZII@o`OU=s+x5m=M zoOn>(*NwpyaCFgk=D%Db*_E0Jbad8?j*rWRWmd(WHsF@l-}eb#pE?0z2H%j33yVO_>Gf$(R@hs`BkB*DBJ{<*w7TOk~NW#^k?~ zc!E|G&b!G$iI#`;rkD_OC-ycnux&z&G{&xB*v7|JAMlL}66Q;!X{Tq(=qB@o5FmDV z)2=eN_W9;rX$6}h=BB`kCp#IQkQ)c%!?VUZ_d3kgNViW&uw8%2IC!iiBX%8P;)X{g zUM$@S;vwFbaJAD@2CZbulFd#FT2_#WU8D;UWxkERgwZxFO)pDUkR>0tWPN!PWaRs6 zJOsB(0xJDI^(CY>-V7@Vb|sPsGV*>YC*LogoWNsiXxI6&$uJqO*P2!j#O6y$JOsE9 zqWNd1*9mZBQ~WX|?D%Ei!CInSnT%NTO}sHuO;aNy(6H0vmRu#3<}t6)OeqsP`kqC0 zLo8|blspe#2Vmf)h!bGNoskT| zW~c7jN_A3qQ;6tX9$zKeL~%PRHApZ=QRl>-slyT%XzIAZio>CBhD^)EmhwWBG;p7k zQ#>W%1%rXGikw#<-={<@ei&;wPQP6e$p9g%AE1SBpKmy+VuHys>`f10?v%)}GB+bPO+n9*<_M~a#!V05LwouU&57}=G|t4)!%f{eWF z$EN5cf|Z`-c4`kdshck1>$_+Mf-KD#)yx)NYw|9dsp1uNX%a=6sbq|I$?l`61+qdt zzMo(dqn%#g3GGa#K3&ARqwRj0swMBS>cIYjiQZyDRq9pz$B zLf7^QK1;F}7f%pKs~a=ISu^&v>?dL~Sth+!bhuh7Q3ygNE(LMrzO*sxkJk2gb#!j+ z>Kwdk%lh?$o7Zl+>dL`Y{hRwqT(8?OaP`K`gR53=TDfuEz~-wrQY`NW4GvC>?ZV`0 zIU$yZc1G>RPJ??Vc~Hod8eXh5qvf5Gbh$0o*Vi@B+rMqwb^UZXMxm~%?C)f)zpIt)_bd2JcjKysve_RpUGXp?`FA<>&}Ikup1z zD#)F(E}3lP?8O3zB6W2E^I1^bPPCY$HnUcZ>`3CyeK()wqX! zWJo29JwO&8ACe0>kObz47MNg5U1qIvR+1<=X5ytZLVR1?`b!rISFP6B8H-zzKaA4y}W6uhQc zEC&buMV?&MaqFDXETa%tPAXNMVDof^Bz0t!B+p|5C1Nwxl5<~uNBdsRU3C01W*w)F zE&uL2?MwCUJLg73*S(VH4&G1ZxvwU=?KQl=&Fgqi8QzoUyf+c;ehcrX^H!oq-cEGE zJBj+^kMe#tALo5!c<-9?{*LH7pCY>D zGrXtHXNhX}6210wM4$ghUTOa&4*xRIuCEYX^i?9>=Yl@*O`-?BP1OBe=6|2}0r~;a zz=K3D{}IuB4-w7#3DMgBCc625iIzM{^unJLec_ix?Y}0viTC8;Emde+OGFD>Bl_B$ zh~9c!L=#VnsQ1K(ZaF!k`%a1I?F%E?eP%=-KRcqiZ4qr=6w&J!NA%67MbvhFL^oa- z(VZ7Z^ynoV))~E0LqS5Ok`s83lkCh|p9FC~EgSXk)#c}V6=!U%!y?;ES zdDV!v*CP7hKIR{cXyfxEddmwUnll~I@XZl@;l-TBmqzr$mqqlQL!6FRMs(Y)5q;%0 z=D#MQm%onF{f3AxdlRSiEfM|vt(@+6aGu{8(U0HFA6VuSZn-*NFc1TO9tKh}M5EqFWz`=$;>Pn*JlAYyUH%SN<25%}*j~|DTBVJrdFV zKjpOijPLymzSplfp5O4jcng$?Qi-Cjr_lgpIW>txwVTo>9$NVcx^vH%1eRhELHnH9oj&m!=c^$|3 zTu#FcC3@h-615GL=*CKkKD?9VM@rN^TB1K0E73g@CHi@_M3+u+UiO#hPTs8NhtKDG zy^zy5U837=<~+Q(M8CeJM7=L7(Y`|^`rx0I=zm^SqKj_hIBze}D_&co&%d7IePfBv zcr&N>FG}>{w{f|=qeMS?CvO$=o)TSmC*SYAoR+&vbkYY(wB|!4s{Kug-t&L92EyCZq{HOn)9pO*%Crjr@3iRD=#*$dbTVt46`d5F7|rLi6QU0C|?(kK7UiO)3W+~~=ICsX)6`YwHk{*As( z-=c5QztT78>-07HD&0^2LSLbOrZ3Y!u^Id#eS!XwK2M*c`{-V}hyH;+OMg$Fp}XnR z^eH+_e@CCB|4W~s|3e?AkFkyW2>mVn4SkqCL?5IN(EI7H=`MO7y_f!q?xeq@_t3lP zUGz@6gWf@Jr*opW(OWsy$v<7n;G0sEB`C}9cT;-FblBJb|Cd&JS?M(X1-*sdOmCt; zr#I3Y==JnEdM&+%Zl_n%ZS-e!E4_+dNq(-`fgQQAYhX@qvsPTD~g8m1xIPG!20 z2I&U6o}Nq3q3h_`w2ijXwX}sc(Z8l)S@cYWhRf&~bSW*TWwexfsfW6$i#n-;+UXKnLKo9TbRk_p=hJ!gbb1;+mF@FW zXc3)DZS-V1ht8(6=uA3;PN#)*8l6g~&;mM{PNEZOKAk{MqT^{E9Y=HNSeiq}P%9lx zNAcD=ymgtn`=b~DUrpVMSTKsq1S9AosN$PZ!Wpu9P?U~{hpJ>wSUUzZf_WS9=fS9kHo3`T04MT{C)0?F}= z^dQVBGNS>ASk5&{#um>&!YGc4$|?(_GBG}sF|=f{W%-M3PE?HC{!bqS~Tt?zev4#gYCTsK;rw0AFaz^!8SWbDC8E_KsPdH#{Yo3DN$7wjCBOHBxZpT)g zE-eIkomtFGc4G-vN(YwVn_hJ0agt|SH>m8ENz0GZWi@1XkaC|XMhD91n9V(2AdS43 z3Ws=<)mPR9ok*I+K=LQS09}@D>RcT02>3Q)E`W?su%Ii3h$RmuUW|jPOW66uH5>a% z6?5<~vSq~F#43~TO`sCD!wf2^sOkdi4wmV+xWeWou4N@)F<}jzn!w5z!bE=U+z6(q z6B4nAEFuf>bUv9}D5TE+6E#O767b@(N`#qJa3*3=y?An>jn`@%U9`kl>2y%#JgCd4a!1L050q zLpN2NV^$2?euNpzHmoYqn1sxwBwTXtP7cZxh#J$%sj4;|3!XwcZ zQQY2fxyigDg1GJDi0T%Q$JCX-ehxAUx0Vv`W*q_5Ci3F8T1FXRtSQW)9l2M&NRn#UTZxFzKziKFz|Q%Qli`bmk^)IUz!Dj9_l6UNtz zC~jxDimJ8cFio??Ve6KcVP?0#Vi{dcIe@Whqy*opez-*z$HHZpY?qm^WEOPAV!ub= znVQ88iQ8*&geloi)ERcttQMS%!EdGsD1-g``3Ldlrlzd;*{*Wuw#4NngJwMcKstn0 zb4*5Tu^m&F-Zb-cP)!JDzz#_?)9fH&)OBOc3lLRda#2{^aI?itvn0ul8z%8fpVE{| zD)Ei8*UDJ7Go^87M`hKB=2ccYrjcQ5n7YwZfRT18NK+H2C?O$h$y8;l8EM}VTB49p zhH*16DPc7Ry&zPpsdq$-arvi8nC4+JR?|pK3t?<}FhfRC8;ofYSglIkY)lig8;{xH zsL*4xX+4beXN+{3=43Kfs~%3zq9vd!>Qp4KeX#pFvt)FYof)3e*i5DwrpViaN*S@N zhG$9~rumr?7<&O(2~E`T>0(xc)VoLAEKP>k&7@?QX`(tIiH52ZW;a*u0PPKUz&0L% zYPzOqr5fHuGuBnJyosb~Oli{Ey8O5k4O=HPwH%e-kz!S~N8;HB*Ddja6U|;b32x)p z4zZfRE_;+4CB6|$xHe8X`3>MWM!y*>gW|@peF=M^y*V*|+hNR>Ie*yYgT;CJ>S2+0Ru^=9JZi+JHoH@lgCUKT8D}0C`#>$aJHN^0Bm%K9Uub@N=w?gePEypp^WqA`OX3N9m-?gXY*Z*K(gv*=IkBW|K(0lYhK|9N*E$Ao9?We#@@I&QJ;HM^>t z*vGKPj4wMml(Or-(MsJFe#+TnUJBQ)AIB;Nlxu-?0G3t1Z&$8Ulgj*86r9EDx}{KBrT|4EDbAIk8JMSSxVL*a&r zde={Mih+prQjLQNO*afGJoBC3!7rKiwDh7FSw`%DtM#9v%7Qb!~lKYC=C&aLc1N?i^amoR5efgi@l*5eT5&kA)*hr`j2RWDq7busLaZ=El-S~5-v z^ZnIrN?}z>?=phQ;FdQe5lwN(5dA?gi|>D2%mlh#K5ig6c3T(RMqfdb+o{@x zDLH8gbwl(=21b&ylmZc`q@&H#5@_B)(U>X`zuUsh=2y6zYI zk|hNN_8Y{v6-~5Um6}*wR3&;6a8t0X1%#TlS1e?WvjC|{v7*&s*((XP=6E0{}=Rq^y{W_>A?0Vl6jlWWA z#8a%WVkNW0rESj*x8cq{yZ5nHxA=6a*~=&6-3$iZJ9618W5llj-v-Yj(Omd6{-&br zSB7612c+z39e0n!T_=!^xmKqM_$l2bU}tueAkgX}QJ1I&RI)e_MxFP^S9LYKYJNDP zXhj2_8>fl~HBskT*Sd|bSXRe};<#NNifJAm-Ugc)cXL=w)4^de(f$pC{X_x8AlxS5(7YV=T= zPBPFX;RZX(ZoJTryCGaHuE7NkSljA=@MOfj|TS;*2q{bY(sj7rlIgL~>lg(4Z(+yL>@tdSz!^5)uYq_154$)timVTIY z%@LN~G?gJ}M9K`fjm|WlZe^hIH`-h(I)Kfv;cKQUG2QHhfZgE4wcFGb906^8W*Ua^ zM~Q)blS5qTCMA@-#w3_Q53FK=1Hdn(9-MX7Cm+l@;`hRe0`;vU83b?d}?F}O$H z{Px32u3Lvd?jC)i{tG$r5-r}OgL?Vo^Gqe&5dWFh60LarsrqUTpS04mBANz4>>}a~ zQPPeDqO23`;}UBx5OEN%!C$D(=8|0kh_xj`8K`84vzJ|J=N`9g;gYnEh+-L$^A=C0 z#B2Rub}4kUChj1Ue-_y4kesE~Rf+d9piHD_JPf97T^M(Tq(E%0)(k}w!^}d`(E>?3 zITOdpw5B47H!h;@sk|V+SxDkdjCg+$c|a};NxZ2M?@^K}2LOrVRQ}~nj(Cp`#o&K^ z^V<$DNgxhThRh};Kp{ZrE zps7GF3KpPkiBPoqFj}W;7XO#<)o(wpO1%AshHt|9X#_UFZiZGUZhP8o+)4|`8($U9 z0(NjQ!2mZ=J*1l=3*`$pyn4=p}AH}qK3E{apl-Pw2tC3>y+&)xKhhG=*28@NP zi@LaEfR)E(af#O~4J+3$PC&rQ`B_*wQDVhvIu#c(!M;p##4i*%Rw-};Qfr0Unx2kj zUafRW0r3Oh8u*C-3_h}K4R2664T4yGk2%pEZw3SU+b9a>$RM_N5+z<`)n_ zZ~}oK-ojQKSqOSEqXz^8JuS-uJxN%gcv!!<;BgyR$T(UBIAo2rj?ZL`6=%k>A`OwF zyS=(8f6dj^<>7|63@%*FwTw#$xH$@d;3Eq+ZHyTZ#Fk{?=2V3nJ&i<_UvcsCWRYk2 zgParZWhoDdpXcy}#!n(s7&r(vK`FZV5!Gbv^0OIDARwxKyPR#fF2LeKDY#v3>Q}NyD>*`N7s@2DMYW9`7)t948bI6_;=^02RMt!jXlFc18pU1^m=3R2&af#B=+K zfDL3xBf((fWU$pCu>myTkl0wsX987mUIL-w%LiMLWqv#AxB}wkLM|HMwM&pPPl3V`ID&7b_=-Y|0;rl?xO{V&xfpWw4S5C$VfOMta))nr1e)l=veM(;#4^ zeoM*B!7eUPFp{ee2^82?91!lz_sJ!jqbt1Sy!Ft)G2;9~Zc? zrJtxs>0*?C!jQsq;hO|0nAIPS6kGac~+45ZRVi=ne4YnF3};}y8fIjN9D+4(BwUPhOm~&nj!4ZK}XT* z!)OK1+j8>EE7ijAnxz$t+YYD#3B=a#nKsh11+H$-fL;R>+{0ut9Eem)VFs;08>Mp2`)uxE;?G=9g8+Ac7cn@J8!f%vcAtT?X?wV+i)Pc# z;Ie{ETcm6i$}Y{OVa_UKwko-q*tA#5x){&EU1KY3Tm2cX38f->rBn!|0qb}k15LoS0(;5vl?zmo(?c{)^V1jsbA=r$5Ss z!^fpu;S<5qDQnl3)t^2G{pl#XBM&c`I{aqCOUoFb_?GTTT8RHk=wL1t4hfy*d?rTf zkc}KxZYHC2{%VccJgcamM{+@U$?MN5iZ>7_E=+=_O2>KW>k6IWnyM0NSDI~Xw}Wj8 z2&k#=otteaiVK@y&)Z}s(8gE_xqYc-n>TVS;{^oFEXt~j#Jmw%YQ@D%@VpV3kz81r zL5j%$ejDUcX`h8V$H=V*SzzFV#a`F?rh9^rav~KF{wp;kJ%{Iz~KU>>uXjGuh zwhaK{!}pCF_d~s0dXutfoz0gJY5hHBl1fM3j;V}sO0vCbEtBO{hc?=R_n}Eqkvd%w1RZF16?SOAPPlA}AV$+xVHG-{Cy{c=$T4-vLvaAv2`pW`g0D+8EYri}Y$g zUf6}GJF@EG)6piBPbQ%{Tj1Qr+0%!42^MJ<|3{$`VUi~fT$QNIzoH?Tn)mSaa1GUi zt+TVnQX+c?Kf0nJ8%-}D8@ngzBMsTz90-|>N^T~|{ssBylnak8$inkUzQ$|;vfHTD zqXq7e7fKxChBzdUH}F{skaO8&{pRQQNt>_^2o09 zwS+NueotC(+eKz17hdw?q^HYY8!?+#kYCO{X&~UF{t9w`ztG}h<{S+y{x2&{^ta-W zw&vMa~qJOA1vab@{ckh=6tLF9to2{SiVzxOT;HiE;o7s=HxHt;# zXET`zv_F}0*uhVI1_siZ++645~ zAKOkS9eEfc3G&ExnY2un%M{v85gI8|_4|>BnS>aAvtg!XvS5aAx19%0#y(GW zb^>jlqmsLZ?CJ1lMJy;Fa?rN`@=C*b0Omf1nkuJ zd$_FQZ~YV(I>COAot+p3_17snD!Kih-cEbBA364NBNuzo#$M2`g0Yvg010)ZjlE!n z24vPNIp05OJTm@A@-^n~IGL|CA-ej#Q%3yAa7{EQ}#MxF_AuRB%svs_GLS zPmeN|KtXs~!?Vb(kJmK^o)VeLgC}1#H=a6|+jVJ4d?{*_-w5?BrAS#|EVvN|MlOfd zx&D{+1*lzGK>df@BmAsEcD#7@jlR-_I?dY^5mX$5NBEw=ZBEfq=l;aJy@DD_gh^#&3^3=TrIzV_e@9%|C#r4*y`W zt_%4(tyi^|`H;Cz$<4$+cz%pu$6v#G1A*ZBD~{NvIBK&6!fsngy9{S@dFn&9vX`k# zT$N}m|B9icWVXXsXQXN7ZX@;f`fH9R83RSbG5Q>VWBz_;e5BzRb9x~&sN`mX<3Eg_ z47!YDD`HiJCk1!#L??mdOR{F_oQ9b?{9uxbvjxm<*RBS$?jSa$vuMUt%+uYt&>bO3ve6D-euwL%gk;+B+eQ?)pl3&*YKE8=kg2 zvbzqnL{`@U!d+ttPR5E7hD0J$!Q&RGs<~_Vo(|h8%3k%^JooYGj3p58RDbRxm*2`= zm7zTnP{qZPYpsAKJ%}Xj(lyM{cFD?69vr!9xiQq+YmdyxnspyPfTDSdZCq0D6wd=V z)RE>X!i9m%b|r^$cE9G)cpk~Y_?d_)N{*}8!A&Ye-JazuE>ymvr+Va3YKkJ;;%S*I zi|2QiwsG$CVYJTGEdGx&CAv)f>Kb;CuQZaqtoL!L1vlYSwe#b;$DWlbaXrq3Q8ZkK zdW@)p;4xyf0>?~n{qNdiOcrri>nEu-TLA4EVcq)4)09uTa5vn5b4JC(-!i8UCY39&qAS5bs z7Z^n#ASt^w8yEY!u`QDYujO1vinEp0n)Nf34@Ge~{2oKCbRXjf6o{cKrpnb}tbHi1 zr<4yvafb3?$X0v8+aa#?ra^fyRBUg11{gwyW`H53HUkX#JF(B^HKG5=_yGY!*>hB5 z(_j1s+v%vi)FHdGus|ONu1a(@|Kc+vXoXg4IzRakl%f^f>Tfcy`lpN?5bzV=Rj)oU zQd?Ud8y>Axi(8ZB^Qx^ZHU03v+&{hP9V_6Va0 z1hI>=`ruEA-`v$>wd%pbLn){)GBr;*v1FBor5S9My8T-1>;hs5+vWmG$2P#yzcG$L z5bMgq(wT9&^Qx#y72Y*Il{C3)e6+yd!F&p5NguL)dzQMC_1h3%nflGonT3vqy^_3- zh*vU}elWV4Y<9%A8Cf8pt6@*KD#L50DpQs9l>&dXIbYAD0EW(#g^5s1S*rPKz9zF) z4>OiP;nu1=HCkI;t&Uf(o~lhu75JO+xvf`!!YPZa}nf$z|%Gl7s;x_eyW=dz|%0fkeZ4zjTOT^L* zN0dFEOCky@M2lvZE>(zXZGfn+F`7US%bs&(%Dy~0Iw{9^%Ohh2o@Ls``O$}T(D7Xr z9MVC*p3jVfzS7N>g^LhOdCK{HDd$Tc5>>N#|AxmIQ6QkIp>IC4t1>(_S`n{ip=S-` zF;j_5g{@CguUU%j3=q{^cIuxPO`s4vwXrfWUaf7dj*so!T%O#sv9hDMRbLywA^K=w zr~GW?uv4kZ`BBwma|pi3hynpsjph(+tgxLQAKEa!uTtQ8FUQ!OouOPvGUdQ(`7zXG zubq^2-E8hL{~_ZC1PnFmF<&!Y-CLG@2J6Olj2G7|<>-b=0S4TD2A5Ynzd^YcF z{~#j?1TA3)Li1lrlApqJ^_OgOUoDzD_d6G6aG_TIijqa%Y;W7sBa zV0^s57AR=nUNI zVGq0!J9~U{HfLOYOOAVIiS4=pvEYDKL5eQg%YL?|W9tA&E z-dh+G=lQZ*$aE zG9%%(#>nTdt=jFK-efP&lL}~ImhjVuh502Djw}oF2aFUD(6Tto!py^4S$vOpfyk-+ zkp>2K7Kb!2tNBbA7@ZZ%T4axh*?DJ$86d{@hG%mts+-XQ0%Gd7qDIHd!~HueW3>YJ zD+jltOlESRM(2Ma6a}~QZ1x8L#4`4Do;dPW<2s87A zn{1BI2F4Bu_{p9lqzW9pfBpQ4RNe(*(?ofwzzZ9L2H!Js6|~<|W8F|sIqjRNvw2LS zlhFhUvPit7uhK*oDb7wVi{xm{z!Pe&*7+rj9T4zSzjeN2WNcV`d7`*oJi)E=I4{9| zP?|=#&GFX`&E^%cJ&YF+Fq6F^7B+i$WU{<{v{G~jI@k?z@)L}mEUjStG}(OnyBIql z;3s>&y}*xHg}iyZRvs<()(%1Q?Nf3Tw0fhyUO0++yw$L?d5q$Hj35wDRDX;@&d3lb zS~)&d#7rQ5LucDj*_LlVAUTeLCt%NQBxKg8atII{W=U&|;0L98>Y z|L_b`?6YTYzC|M6@<;Y2K2vdIZ(^CRL~o*+-JIBTFuaCK-Lr5;Jw{D7+y32*D-f`i zJ=@;GR;5>tyLmpd99VN zo*!FXOTC(KHm^9lm2m|Ewz5~8Io4`c>;l=w`O$~02UAv@Wo2syxcV)3gKVbHvQ?k( zc)E?T1j>si?2y=WctWOTh$p3*A5TrTPur^*OCaDWd)1j!HDIi_*l-G-J{vdNbe{5~ zDMd55m$S#;^Y2`K5Jl^o>St`AZ|Zn}gb#S7Yl&7o{#1P>_xyVY;|K(?rCEJbPj;{* zmdwnOz1p6R+^R!5<}0kL67AtDyMwC1erxq-z{SY)OUS}#Fw&Z=7xe9nG7yj!)C)Rm z_HA3FAccPtWc7PN6B)Cxkq2MD)&6ZwX2tGgT!HdgvBXYg#dWo2WW^G>%Q=F>J2SHt z%Z?-1iZ$5`yEih*KtNjk8Fq1Puo7QWEckwI`8(WAuJYTjRNY`iHQC;iZ)G%rfT--9 zavcv$lpi~>Mc>;kXz$57Ih!G};*zoS!_nn`_w^ipG)438pT%f_e?J$n;RAk0nt%U( zMivNS-C6$qS?)97{S;=!K5vVWadk+)>vHR=L>0bro%@;W^;WNJ7-0E(Bb@K8%hge{ z8DXzubb&$<=I=&TT*oUX!u+gR7AVBGPmLfy!kTP+=QWHj5D*qJ$HCs*s<_z7Ilg0O zDmSWr%U!!V{64SQJiqu|j3p58)L?$Gn0GW#s}_5@OwcwtrXExI3Hx$%UA17Zs?(px za4Hvn(Y&hA{VU$W=m7yiA#+0qs^hiFP>l<|xb`S#2fWErZXCsh0nPliTaS;WwgO_Q zkFfy{9lQGA$nwztoN)w#SZ|hxevb7T^0H6aJ5#CZKEUFlDreUf*0d%wmm6OhapXr@ zZ@V}9YBrDcyq-}80@6apdep<1Exirr;Vn~#Y!0rpu1d6*udL=^YVKx;HMfZDNWyDK ze-6!p0$R0hE<{*0YGBpg$v6W+EPHod*QyoU$;!C|2k=6MY%qj00d2kf7OUG|gEN~~ zPP~~B1Okde#tcz-7~B^Fu+_?J|A?6Tde|AuJIg&;Ff-dNp~=woCPo%054r@h@FWVo zZ)(ntcc8=0*i39zQa``#YO-FdI~Z9Ypev-;%6`3z>(A%xwX!pna|Ak}njcTyou2o8 zCO?d#^*Lar06eN=02Ds5+KKOBEP)`_k=0H--BcNg4c3bbsYN2&@<;mZb*IlH@|CwH ztfL<8tNT3=v$;3xFBw%JAS!*5L;k zM<8G+WbXx8@cjepiVLNji{|4T^>wz>+WxMN&aGXYAuYdD#bBG%WZu);7(*c7D8zfR z-Ou7eDW~^jXDa8IUqUs%_ta$H#`tT-5(sz->5mdUdi_OSf0J|2PiH8n7jCKL$56XJ zmuh|i>$cEuW8Ba90RcnxcXi!R*^31dg&yxajdP(7>2`OjOI(%cO8ynyn&=$_(YW}(Qe{E?=n zOL3&B*}zxY)acxV_dzny(v+U4*tv&s0}94Yv3I`9iyh>~HX-#}UJ5%&r6($OKF_!T z1!L!`%7I$**d631x7|r9JyEf9AL9lTjGbZ+_vf`cVyr*7$4A+nq*6F`{Bi!1xZsQC zAEQM9f9El)x=-_uKgYNM0Xy}-d!Pz#%}8~!z=kJy!(Tf)xh;<<1Zd@NUp3ixGwx;V zfP(Q;@O@hI;zw?^%HO~Z>u*-5i?yEc`1t~32NaGUtm!STj{sa zFEMsNLHJqEpUrI|`x9p;7k(VA{P=12zj$y07k<(FpU`huewncY0)Fa$C4)`(+Va@& zXo0`h6a2ZbI4`-7BeQR-1bfX*HXiXWj2IA5({MZj)2W(fJVIs020_Dq9!qBi_NKYU zBfiL}0RcG;$0Ja1#dUS^j7K0RxiMoa1Y4RW8;|%ZV+91fG#rl*ZegKKO`g6Tots>( z^E9PU?4*5b`AJ;xMe}V!zn)uR+<<_c?62nzPVO2RoIE&L?7@hWI1l=e16ddW#vuo? z`uS|o&PqUo+!4=RGc_Aph8ZcKFtilAYRZKcWF-e$gm`YW^tAha%xrEq4KY$cKudPJ zNxxw{oojrkxSq_(up{b_#swx8hcqr&%^DjQk(V5JapgnnqMmLaFSB_K#f6L&5b%<{ zhC<+Fv{K$zSy>&ajSTU};dt|gk@2zOV&`1IL>&@4_&5~~iJd_{lh{eiSI{?dGfTzN z4#ri=toPISp%l%>S<1zR@v9a9h7S~G+W6IW#uNx***kKJ5*!&DsST8OR<12qNAT^+ z;$kXj!E@~eMSYI=@T^A?q()vC8?*9-}W?F z-_Zq(84z&Opzo+MR;yH-uUsmr=g9@#IcCe?NGWTaTul2f-F$Dsyvd31%`#4+~ zt<);$Ju0q~eF|qoAF?MB%WHASp2#csO!h>Y+{FDp4G@%~5ssSXs>Lp2#DD^7FP|vH7|DB*FShc~7Cu&bhEK z>X2q9YXC%km&wUi*xZh+LYdhLj3(21NnR=oAQRngwtCe4( zAD{oOrK9`=TBS{mmW&LSYvsk*2231B@$axY*Z=zFw;xt=*d|&WklUrs^*_knz)u)t z$BQT45<$Zp5xwkEJ5JbkS0XYCNP+|i`*0x50g<4>3)>g18zkNE$( zd1+k=Su>%Ujk6s;7 z+?HQ1{OIh2?f0rtXKUrRKb`&;`DXL{$=!?{5b#rH{-kW>hzV~alRU(-xjb3mp}DY0 z0%>^)>py6q8Sek|dj8LBUK8+U#t{fus+^XBmyuOs2j z35$!Hpk=Y}U+nDUsjK2Lm{C7>hhI;f%tc?cH6z&C6l+EvXTpJ=$h0*hM=^3hKu?`H z+E#Hl!=^DdeB}O9%R<`g$x^UT7hGdC*}U3!b8Q6ze(H?4$&!QN*;skrRE6E<0)G`R zkIU?2DOU~VY38rVnrtVT4=|2Ez*3z)LxZJ4_ZfzDHanTgjUO(68TmlHUG_M|(PEs9 zS1{9YT=INGOKQ9atvnaM7=QaF)Atk)PpEBv+l}_P4uUZD^$^p;xZneFS>tiP zXF?7iKo@LpWdNXCufa5uTMf1Btzs2w_wsRmnqU^noEI=N4Z1J7d zk&P`ak%cXUhYgMx2;{bMwn8voQr33Q=J_Z;V61?Empb!NR4G?#HU2W}WU+@w!X`HP zIkMqUXR2;}#c&+?Gj?b5ib}3^r$N9`Lx0_wo>*K1l*eCpvJ^g6!hH>%W`2LY$>zxM zjH+o+kWJzh&9h*%By5hHG%a?Hte${|CMiQN*djIAsO@_gK_H;0{;0or33pVgm9e1$ z_g@B&+S=L4Ri|yNs6v2Nek;^uyG-20*Z~1Q4SO&*R>T%mh3+^wgEOKJIe}Eqcg0nS zuHj$ZR5kg@S(oWzu+@SsP^UdhSI$*iz{Ov*wiu@Wq7R$^4L$$bn*G2a`XznAG^7!qVWcjMGI75A%-QnHf26`RP%WsF8toQPbY=b~RQN!L#Rd~hq zsq^$+s?6A?B)|z0sDsWE9yt#%YCu8A;i{|H&H6mZQJJwj1REm9(#emUCi8v1!KeWN zISqZEf$DgTcaUrv-^b)9AAaIuu+@TnpC;Q={WeAq2ncF8&Y?eWTHIaC~jz3>^HqRZ|&$t1FSsyOAEn~%==Xi$ny~S^%mKV??^OLLXo~{;-Ab*wZ zY+he>3!?`VY<)29*gVz;xyg;5q*6F`n#=m!%(wvsTc1t4sv~0so~fVL`pEp`$B!rm zTP+wtJ$}CrW?dAm-{)Cuz|rqB7tr7%tKaAOj2;lgy0gA-f0i19hvD8-scqU@9vv0C zu@&1CJyqmf{>ZAVPjPTnqMiJ!I}uZ}W;-}RUXfQgh#`Lqt;?UCy|927TgK%FjPcxA zab#ia0OJb;u_aj;JJrP)w$>^(y7Z0)$ht(=rJ@w%Rf(R%R|$0KOw9yWwr+kzHCb=< zKQo#@KvcutYV~Rtdmr08z13ERW`ZOX#LVnclg%f(i4g<>iW<%*QiZ-|bbP#8T<<&2 zd?GtXGr^H43R^MQhwApd@U!_L6wQZvCgTG2+pz!$A6fO=zc7YC5bMgS-_CUFw^bOw zzGkX2Rasvt@Yie>i;T-3S;<|lIJhcNg?|&3oRc#f9-$soW(2|bYBC@F%Zx1$@D=K# zi$(3crwA`J85=rST!S?OAKlH`Oc3VBkl&+nRu9PW|Jl6m{{@UM5HQwo-M^|iR#rwv zd5Udu!F3L2Mjz68EmxP)dOe%364p!QX-24$#bK)kTdZ!sPjfbZ-R%z)Ps@L)9vjk@nn81Mf0`b zR>0Rf4v^sk#krPf#p9xaz?Gb*^v8@U5X5@2e66!BUu&Q$r@NJF75TmT$>O5xX`Cs2 zNObk9ONp)#zOvEf=PftF>PHcbvL@@Xnr4K7fU=+-E2|ib3$MIAR#v8R&1-@|Pw3|N zubOO*!K)ZeARsDej=@#D`n0%E$~(sZxyfytl1jnWsL8Oik8uME#twgBpul5%d9j1s z*h6(eo>NjO6g$1WwkIxEg->$97j0EInl@M!{xpC>9cin=(cpm$??5aL->+)CD!j*W zI@a-LbFqA9d~EdrOnOGdmt=Wjr&*qut$5@un<Q&-xvMl2Q zC3=S9NI&g*zOwx^JLkFPm!l@lQVvCQPm}dj@SY6QAUw>Vo(fZdM;>+RvGc!d)M=S4 z)FIr(894QODomboduHjX!Dwo-zSZY3l0ZOHP~R$w4R3j zzOLTzwJ_RNIfa85^85bX{+`afHmPuZ7MC$*&}V^q7>8fC%07z;#uo@;omn;I8Ht+G zD#qfPt*3~5%OB*P=vneYb%zb{Rk9+qGBzXImC(**k13Np$ zn+ZER#%Ex8T%};_bou+Pp3Vhdw6PpCUEmk-j=bW?@{7L8xB)>ds0RVxND;F`*zqnd zhT3GVat136DZgop#Y1ErMz_+ax)_g*-GKq@!M&$c^C0l zGHyV?PQzVz(A-@!Qk^WYBXqiFh_D+74%tn4HJ{n7Gm)L#qcpKr{+3vi)o1rJc0j@P znW)R~$`*PSRT~ha4%x`B-`j2HDmR+4l%J?J>7|S(P_RvEqOIv+V&%^CyvJAY4!e$4 zew)-}JIme9*a3y(XIG`jgU5O6G%?lJc5Z@3k7KR;_-V2_t;*N|1=nf)wOV;-mmC}^ zu3z1zD>VKeQm5Iu%3Y^rDL+x`w6`*zKtVPM6FHiwPK&dX%O*Km`E62@?Hm6l#tsPh zX}E7ZimteJXaQ$IAJPt8qAq0UF_CX>;)D7#z~(tm89nb7Wp}A;(tX5MMw602YC0}XSsLbTTjir;{!)dZnj5ByAW)P6ma711eT!FV@ z&NGUkbCau;m8KMKb(*WL`Z!x0P*7ckf-CY|pgeVz%u8;Y!*hVF?!{o#G}(7dzQc$C z0W}TxLP1Fve;hu~mO6Hh@_ff6LoXObP1Z7hJtGJN6g6y_qwI?7CFg0GBPX^IY1q0- zDum*td%6E5u6bP8MQfQ~#Lpis^Pe-}fWMg5GJgwW1q8e-%90IE8Ld=oFv-}2na*Z~Du9`Qk-rm3}bZgS0#Pg4rV zPIJvjp5P|*Z~1Q=VsMU$BObE zE%Xe)6Ol`GNK=E(a~#stU}IF68ugvEkdG6mNeAO(nLh^y-w-MqPA=eL0Ztxe!jXj& zUaB+=0#4epa56`f*p4EPFU%LYls^(CcvEmlm|z=X2@}pnUoc9f@C<4r-`kzdD?fR3 zW*P*PWVf>!CHN%KMr^7-Su64q^o0P1IwV>!R~Cmv%LYCJTAb{Jq9&=78$HeD_i|}Y zgMgm8elJR{(4Bjsey_?%9>j3ISv)^hmic}!zQ&S(@Lnf^h-@ zR-T+yA04A!&EhtYPCy>jfvn;)T=KwGN$iKI4NM-CShArWOHYUIu`DPcLb|zNfDpo? z=K}?mhL9T=9UzEhf7=WQsnk|itK(HU;kv*dr3zZE;AhCfLiU83RJGhzrrAcjk7E3Q zfT8S}5?prJ$d_H2>gMuffmcZdb^IoBW3`d%qQ@=)ODEX+G}%g*KVZ~=fSl}=E~2bO zS81W$nV{LjPF}*s3o=zgQPXVm_<9*JAfTqM2fR^!jHS4aP0&0(^ZfJTWoGmEy8YJn zY~Bxd4kHBwv}Esx%Y{5p9UrPpPO_tOMRk1ty0M{ZWpAa(Q)?D+j`Shj;*`%rWoInZ zW|sR`v{ZJObWaW@~x0z+O+N zDbhI!nxSRo+>#Hiw|d(BuZhj(b-i4~(;%RwuD63SmOb#rt-6ASxz+Q}H=1B_Z&|2N z%ru*YVH-aU3bHWhC@U^v@>m$;#CrO5+f=|yLOv8N&1PZFVWfb9EX<}|)se9R*GPFR zjLb_e#E9Hm8o`+9@>fOi>gmGyFqd*I10SXpVBjOmhvDvyX%NKPv-*e^Dof7J@ut1y z(b4!jp~W>fZOF7bkoVM$k+ZU8c`J==PqIu{Ik#ur)8o&Wn9W~K<(GFF1XN{zIW?{L zMMqD$HeTQ^=AcjA)Xm$B@fDYj6+^J)YO=8lUNk!m0>}`Dzu(kVp2n zO^U716QalX;a_Y{JR%N}iGBR4|o#Z()@gW-km=u8nS0xf3 zBrsM;<;Oxo2zq?2VElCYec`8Z;TNqhd?^ z2<&Xmj6NiWxJDO;#L!kg$IK)3PFQ)$iKBGQU@Ud}wb+>j#1cN`04yET083o#(;$d- zWc5j$uF8F&Du;Q?wThgaGx!h{9G->|0SOASx+1iPlJ4>@#N<# z52osB2ji-N59M~89!)R16a~UneSAo73ym2Z(%ag?XWHAca~-ztSxRx)ilGSZX|gYb^V-2_ z5FTQ~egsqMM;>W-&hp4cnwH2y8p2&{fupU2MA0?O@llk^!!pHSs|6#d%U_c+n@6ON zXY_!8poSyTD7vf4dn?28BT%ak6c^ZrnU>!CuI40;ug0$}(+*}8Qm&;R8*a~@*~1hFMqy$YvVX5GS7aZ#0LuqA;D z+t@Vh>`c+kkEkYFzrtSTGzf@lIHMB$hfW z5wZtA4FYN!_8$N>#Wg;8T7<}nHQz1>FqV8MT6&snm;3V=DWE{K>=-XDT6nTfd`JtE z^8LBk!t5B&SwFc7p?GOF>%xoWr$Iq@DfU+p@>mz~k%JulO$qr>wDh$5BO_YlRv3yv>D}r!ZPTL5L~%w{-I$Mt&PUhlSA! zPk6+%Gg?4lh$;3L;d3DdS+P9Wh90l@4gxFWM@*A_AM8nt77!5AaJ<6);CXR<-#i2L zc8>DYQ5kwqRNsbsSEoS@?GDBso1~_Svl41|Y=zu*r@P7aOXHs8X%G-ocfT~0T5)X+ zH~->8wzJvA7YDA)erd=>9-LUR;WnkY*6=@z(E$QN>h}1WPns7OC#OhI#K^#Kn>-{= z#8&m(_A`005Kz~fN!19&OtbAC-N$$V0W)=XkCxL{i;I`g-J{h<*#p~3o^&`)n(K=O zJZ3o!0!|iXjZe4- zidiv_@P8a|>9}@T@wnK%09SLET8U>$PJ@7ubFylkR@6F0{$c_57R3khhPj9Gz*UKs z^DidpaVCQCAw+Z6G(GM9{xI_kh!42>m^JllCLCG#xRJ2|0zR_8>c&MYwujy_wtr-7 zxVVNSbdrmenZCg9Ex9WBant3G6V2w?cGodxK)_Ai*>+SNID)t2?^2REd%zXdqI& zZo|OU8#fQGTD@uI#&rXmuilvdiS#VDNAKhlc`+9e)|q~XQ+`}j;uoRLF`vWOF;-8o zz93|Yf5iXzUmcy_eUN%GaUStxR&i#wsSLI7HJ*&UAQh=XZ^S-J4!k~UJB_>M;)15YDTdy=oyeOJxdX7c1}cuBB|An&E4bw2vB!yaMse8 zoqOD+CDQqm;rdr|AyXoIeY`+Rgwnv7JRpTb_pgfqkVA-a5!KZ033X!HB} zp4~st+rM^Q-*wxra|HH|4_6SnmicpsD?4_KmUm8uVyt87a%;z%NMNjwQ;F2#uuB=& zD9xm;7In)s%X&{OPR$T>aoF*pY{H%zwl=eR8L;GY4RT3y1uk@E1Kz3SU3Q%-->Eln zLbPvSrw{1`QtQ1_2*>earrVj&gM|ZF7w?pM_jDZCV5`b9uTpaO2RZ&Hu(IXQLz}MM zdgujnUh?=AWqAx^;c$F}}Zud?d_aIK><)P~rzT{u0S`U5wCHK}& zJ+ylMp$+o}uGxLjwOcm{p_d$3DRnbz1i8R2fuHeyFNNr*t9P(2A-yh%r0WEfYh6~gnyt)xs|W& zcNgI}%1r0-lfGF#ZGmoE?mi(!bQ=HvFw1;S$st->?z>9v4F3NwEc25LxnC){N&f!} zmKg;e_(-O2Or?7dhrNeoP6pojz(4!GUsrN%%zp+4K1azV)AtJ{2gp}g=2j(#WZLm9 zR_R{E{5x3Yvx)e&yp01$7__cOm$y&N7q2=Sq+^t!QRc07^e1sgZS;1N&|~9p-nED3 zY`x}jF>UM6*VxLPusez^8OQT4hwc^z3+aI4&`KxqFW%h+++}JF_^Hr!LI!eM`3%Ns z0a=MWG&S#pzn+@&qGjYK^(Bu^MQaW{lU3Me>GYkyJ32Jy+G`&Fs!rUsB8O~dIdNa} z61R|*W#V8%@f2Id)*3SD+}Ep<#8z_Np@(1{*;3BAMnro3-Ke5a5ptSYV9ue3*mxfg zq1_h_$TGVAZa7u2Z>?6oI6qsbPSRz6tStLaFQ7wHb40FmCG&!LT*-VfI<$J>p$!WL z4sDr#jmYQ5t^5=c55dmo7iB&MSf{*0LBmv+UAX|V&jVib8e1;d3OORbzl=8i`6H%>hna8)dP(^h8r{@tK zr(4#CIK8#gwD6(Ta|SH&uES6=WdyT+uDX$V-Y*oT!ia3t(?hb&rL;$#aB9PMh*eMmq9HE7?OLUOn*tx)4+!8I~-&iC*Tc=pZ3N6HQ zeJq4hJ-}Ck8&+=^#1~Y?hwAzeBYP(X(>;kn5d^2j3^T%?NcScnl;(JFD$vqGDv+XT z#k$zRlW4eEW+wr2WrQ!on~2F|v64{f>tsJhxI88rY=hJ26q${>lX-+)ijzPrd^3ZP(LL{Ee zxHOQ44l9O_D8IC#@_C{`v3u`C(x=q@f83N5hwh8%Xzx}*D5Dbfa$n3-IXUQkkaJ{!EJf6jEG$~1C^viuMJPx*hKg`MaBEO2k8}ERN8k#fvw#x3_n24kW-@ zgg8r4JeNXZdn2lhOLB7U2I<=KFkx62X<^Jj!P{Bu8{u zp>3&-5*^FY^)24cyv5I(8llTC??g9M|F(5&(X8Zl+iLdjf;`&LFP=ElQ(RkNeg ziBwUEj7lM8Xq|jWOJg$2DHd(i9QLGVd9UhtGxlVV%l^%>Xd@f;V7-ZHnCT;Uetaxu zf}OBmA@DzfAC`Ht#5c9^CG3t;L+J1Hn8|$}M2g;lpS$y0y!;v;MH=t^sJNf}y_=8x z@!)Y>z|=}N@-JMiR(dY~;%#lEZTt(1+e(}H_vzvcChBS8Y(4)zRh+HmUp&lKTE)Ll z5odk;yGWd&nmt#XE#u!dan{Yhu-mO-^%5*WE1{W%28HaXJ4>7))-~d+jek*5wTc}= zVY6FB>jjptRoJ$L;taO_G;ubMf6+2(6?Pq#zm;Ckzp#U?qN7u`d~W4yw7FYFCn=hJ zt@J1SiO9iGR_#-bx4f7x!tU8vnxTx6%av!b-K$DE}TS&OXGy zuVz|6ckwUm>H_*p{)HV}KzH!(rQ+-_`1eiX?2Y_e5ofRA-=6H=0$M<~ z@b9z5*&hBySuLO){Ck@?EAuZ>u|Pak*!u;d-(a6O+sMDG#n}e_eUUiBv%FHAt>oWn zarP|!y-J*+j@T{E@C08W&Mx8K=ZiBSsa>1_7l?C#c;36j8J-@R%?rd+`BQO*(%vS{ zP>Pp}vsV6{!_*2JAWAd6H-7< zBIdXzK8odSPNzVP)RMJz7Xf)Tr(1X&Hm66t3pS@$q}%2!6>p-=Sth!%ZO(GxGufPe zT9&x`3PJ~vEwhqVBsi<+iUemhtxs^)h-YcV)6q`Xx-uOd08i{6*ubkhf84xb2bN0%7l!W$+r4P;6V-pm0e!?}#F z&T=?g_(~sAtIU@gEs_IUq7TXWBfi8NY+^<(@zxp6$M_1i!f@{8E7&x|A>+B9&-5WV z|H+qV0~nc~^3}}_2a7oLA+=6t4s4LQSFa~?C38?lMrN2#VVlgo4tiRzX3jetnfEg1 z#ZEk*V2(zMOwYY49{iJ>`}uU*(Rx71FjL7q=5bDDW!PpD&(oQ6yTe(=oQoXJHS(6s z(=qc_bMe93*NT1vSr;Qf2HxbzK>Vd1=QYgHSe1cy@xKX{H7Wdrr**H#!QjELqxA@L zU~No1N4JphKn!OgbNUR^*3g+l~N6K8~X$>={&(YfN$-IO)h}oq1Rm_2xCpm+Il>-wz*#oTuBlC8axsf?0 zJ)dOG>mAM)m^12dzR4VYATI7R@;Ej~eJ94B<9^h1t|%FF-X`Q$a0l|9e$%ThpC}9M z;?CUt+-)oSEO=Xp%HHwnLGiUS6$N)2th{>r?#fWDuW!>8=qu5k4yp$_PKnS$pGSOi z#6DT6@wK#fGG*u+mz@=xYQrcPp5rW2GPH{mf(+{(;tBK~$NedBUyAO$mXfQc_D*c7 zm1`rDwUMF8Sla1*u{4sI!H32;h7^c*Eh16eK)4|6u;BUcY& zh;Nd3f)d37PyIU%B$Cj_z4j<4`Q-hQRRaRfqsFbLCM&qN?3q`^GqPhxrCJ#q;^$(D zUYDhQ1nA^PC?eTAGA1vqB!Vx1;*>xN9u(3=Wbv?txvxqb_fJ`eo`5!N=2b4T>D)&rDZ17*hQey#hK?6C5-`! zaZ3Q6M= 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/codemp/smartheap/SHW32.DLL b/codemp/smartheap/SHW32.DLL new file mode 100644 index 0000000000000000000000000000000000000000..42b2f67e1d076e1f66750827f43e35c4e3d4fe8f GIT binary patch literal 112720 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 z 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 Q);^4@xszbMn4aVQ4_fB7S^xk5 literal 0 HcmV?d00001 diff --git a/codemp/smartheap/SMRTHEAP.C b/codemp/smartheap/SMRTHEAP.C new file mode 100644 index 0000000..925f52d --- /dev/null +++ b/codemp/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/codemp/smartheap/SMRTHEAP.H b/codemp/smartheap/SMRTHEAP.H new file mode 100644 index 0000000..90bce42 --- /dev/null +++ b/codemp/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/codemp/smartheap/smrtheap.hpp b/codemp/smartheap/smrtheap.hpp new file mode 100644 index 0000000..dfc8435 --- /dev/null +++ b/codemp/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/codemp/smartheap/vssver.scc b/codemp/smartheap/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..2d62d8863663b4904b5d7e96780f164cd489db1e GIT binary patch literal 144 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiW+l*oQP$H)K%=Yh1!z7vPCx3I;7gf0LHmIpRV za&2_M{EI+-FT>KQ>p3jJ{7XQ7S}R+qcJjqIkowC&V&l9d`N=PDfcaN|{1vQM&Tfx> S3Fcn~^6SGc>lklp0`mcE2`Xy< literal 0 HcmV?d00001 diff --git a/codemp/strings/str_server.h b/codemp/strings/str_server.h new file mode 100644 index 0000000..5301bff --- /dev/null +++ b/codemp/strings/str_server.h @@ -0,0 +1,23 @@ +#ifndef __str_server_h +#define __str_server_h + + +/******************************************************************************** + +Server strings + +********************************************************************************/ + +/* + +// findmeste + +#define PACKAGE_STR_SERVER 0x01 + +enum +{ + STR_SERVER_SERVER_NOT_RUNNING = 0x0100 +}; +*/ + +#endif // __str_server_h diff --git a/codemp/strings/vssver.scc b/codemp/strings/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..725fcc8a83d80b5ff5a7a0160bd7385becb9e8a5 GIT binary patch literal 48 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZJpUV2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/ui/ui.vcproj.vspscc b/codemp/ui/ui.vcproj.vspscc new file mode 100644 index 0000000..312b2f9 --- /dev/null +++ b/codemp/ui/ui.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" = "file:C:\\projects\\Jedi\\codemp\\ui\\ui.vcproj" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT" +} diff --git a/codemp/ui/ui_atoms.c b/codemp/ui/ui_atoms.c new file mode 100644 index 0000000..0c023db --- /dev/null +++ b/codemp/ui/ui_atoms.c @@ -0,0 +1,493 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/********************************************************************** + UI_ATOMS.C + + User interface building blocks and support functions. +**********************************************************************/ +#include "ui_local.h" + +qboolean m_entersound; // after a frame, so caching won't disrupt the sound + +// these are here so the functions in q_shared.c can link +#ifndef UI_HARD_LINKED + +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); + + trap_Error( va("%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); + + trap_Print( va("%s", text) ); +} + +#endif + +qboolean newUI = qfalse; + + +/* +================= +UI_ClampCvar +================= +*/ +float UI_ClampCvar( float min, float max, float value ) +{ + if ( value < min ) return min; + if ( value > max ) return max; + return value; +} + +/* +================= +UI_StartDemoLoop +================= +*/ +void UI_StartDemoLoop( void ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" ); +} + + +char *UI_Argv( int arg ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +char *UI_Cvar_VariableString( const char *var_name ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Cvar_VariableStringBuffer( var_name, buffer, sizeof( buffer ) ); + + return buffer; +} + + + +void UI_SetBestScores(postGameInfo_t *newInfo, qboolean postGame) { + trap_Cvar_Set("ui_scoreAccuracy", va("%i%%", newInfo->accuracy)); + trap_Cvar_Set("ui_scoreImpressives", va("%i", newInfo->impressives)); + trap_Cvar_Set("ui_scoreExcellents", va("%i", newInfo->excellents)); + trap_Cvar_Set("ui_scoreDefends", va("%i", newInfo->defends)); + trap_Cvar_Set("ui_scoreAssists", va("%i", newInfo->assists)); + trap_Cvar_Set("ui_scoreGauntlets", va("%i", newInfo->gauntlets)); + trap_Cvar_Set("ui_scoreScore", va("%i", newInfo->score)); + trap_Cvar_Set("ui_scorePerfect", va("%i", newInfo->perfects)); + trap_Cvar_Set("ui_scoreTeam", va("%i to %i", newInfo->redScore, newInfo->blueScore)); + trap_Cvar_Set("ui_scoreBase", va("%i", newInfo->baseScore)); + trap_Cvar_Set("ui_scoreTimeBonus", va("%i", newInfo->timeBonus)); + trap_Cvar_Set("ui_scoreSkillBonus", va("%i", newInfo->skillBonus)); + trap_Cvar_Set("ui_scoreShutoutBonus", va("%i", newInfo->shutoutBonus)); + trap_Cvar_Set("ui_scoreTime", va("%02i:%02i", newInfo->time / 60, newInfo->time % 60)); + trap_Cvar_Set("ui_scoreCaptures", va("%i", newInfo->captures)); + if (postGame) { + trap_Cvar_Set("ui_scoreAccuracy2", va("%i%%", newInfo->accuracy)); + trap_Cvar_Set("ui_scoreImpressives2", va("%i", newInfo->impressives)); + trap_Cvar_Set("ui_scoreExcellents2", va("%i", newInfo->excellents)); + trap_Cvar_Set("ui_scoreDefends2", va("%i", newInfo->defends)); + trap_Cvar_Set("ui_scoreAssists2", va("%i", newInfo->assists)); + trap_Cvar_Set("ui_scoreGauntlets2", va("%i", newInfo->gauntlets)); + trap_Cvar_Set("ui_scoreScore2", va("%i", newInfo->score)); + trap_Cvar_Set("ui_scorePerfect2", va("%i", newInfo->perfects)); + trap_Cvar_Set("ui_scoreTeam2", va("%i to %i", newInfo->redScore, newInfo->blueScore)); + trap_Cvar_Set("ui_scoreBase2", va("%i", newInfo->baseScore)); + trap_Cvar_Set("ui_scoreTimeBonus2", va("%i", newInfo->timeBonus)); + trap_Cvar_Set("ui_scoreSkillBonus2", va("%i", newInfo->skillBonus)); + trap_Cvar_Set("ui_scoreShutoutBonus2", va("%i", newInfo->shutoutBonus)); + trap_Cvar_Set("ui_scoreTime2", va("%02i:%02i", newInfo->time / 60, newInfo->time % 60)); + trap_Cvar_Set("ui_scoreCaptures2", va("%i", newInfo->captures)); + } +} + +void UI_LoadBestScores(const char *map, int game) { + char fileName[MAX_QPATH]; + fileHandle_t f; + postGameInfo_t newInfo; + memset(&newInfo, 0, sizeof(postGameInfo_t)); + Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game); + if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { + int size = 0; + trap_FS_Read(&size, sizeof(int), f); + if (size == sizeof(postGameInfo_t)) { + trap_FS_Read(&newInfo, sizeof(postGameInfo_t), f); + } + trap_FS_FCloseFile(f); + } + UI_SetBestScores(&newInfo, qfalse); + + Com_sprintf(fileName, MAX_QPATH, "demos/%s_%d.dm_%d", map, game, (int)trap_Cvar_VariableValue("protocol")); + uiInfo.demoAvailable = qfalse; + if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { + uiInfo.demoAvailable = qtrue; + trap_FS_FCloseFile(f); + } +} + +/* +=============== +UI_ClearScores +=============== +*/ +void UI_ClearScores() { + char gameList[4096]; + char *gameFile; + int i, len, count, size; + fileHandle_t f; + postGameInfo_t newInfo; + + count = trap_FS_GetFileList( "games", "game", gameList, sizeof(gameList) ); + + size = sizeof(postGameInfo_t); + memset(&newInfo, 0, size); + + if (count > 0) { + gameFile = gameList; + for ( i = 0; i < count; i++ ) { + len = strlen(gameFile); + if (trap_FS_FOpenFile(va("games/%s",gameFile), &f, FS_WRITE) >= 0) { + trap_FS_Write(&size, sizeof(int), f); + trap_FS_Write(&newInfo, size, f); + trap_FS_FCloseFile(f); + } + gameFile += len + 1; + } + } + + UI_SetBestScores(&newInfo, qfalse); + +} + + + +static void UI_Cache_f() { + int i; + Display_CacheAll(); + if (trap_Argc() == 2) { + for (i = 0; i < uiInfo.q3HeadCount; i++) + { + trap_Print( va("model %s\n", uiInfo.q3HeadNames[i]) ); + } + } +} + +/* +======================= +UI_CalcPostGameStats +======================= +*/ +static void UI_CalcPostGameStats() { + char map[MAX_QPATH]; + char fileName[MAX_QPATH]; + char info[MAX_INFO_STRING]; + fileHandle_t f; + int size, game, time, adjustedTime; + postGameInfo_t oldInfo; + postGameInfo_t newInfo; + qboolean newHigh = qfalse; + + trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); + Q_strncpyz( map, Info_ValueForKey( info, "mapname" ), sizeof(map) ); + game = atoi(Info_ValueForKey(info, "g_gametype")); + + // compose file name + Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game); + // see if we have one already + memset(&oldInfo, 0, sizeof(postGameInfo_t)); + if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) { + // if so load it + size = 0; + trap_FS_Read(&size, sizeof(int), f); + if (size == sizeof(postGameInfo_t)) { + trap_FS_Read(&oldInfo, sizeof(postGameInfo_t), f); + } + trap_FS_FCloseFile(f); + } + + newInfo.accuracy = atoi(UI_Argv(3)); + newInfo.impressives = atoi(UI_Argv(4)); + newInfo.excellents = atoi(UI_Argv(5)); + newInfo.defends = atoi(UI_Argv(6)); + newInfo.assists = atoi(UI_Argv(7)); + newInfo.gauntlets = atoi(UI_Argv(8)); + newInfo.baseScore = atoi(UI_Argv(9)); + newInfo.perfects = atoi(UI_Argv(10)); + newInfo.redScore = atoi(UI_Argv(11)); + newInfo.blueScore = atoi(UI_Argv(12)); + time = atoi(UI_Argv(13)); + newInfo.captures = atoi(UI_Argv(14)); + + newInfo.time = (time - trap_Cvar_VariableValue("ui_matchStartTime")) / 1000; + adjustedTime = uiInfo.mapList[ui_currentMap.integer].timeToBeat[game]; + if (newInfo.time < adjustedTime) { + newInfo.timeBonus = (adjustedTime - newInfo.time) * 10; + } else { + newInfo.timeBonus = 0; + } + + if (newInfo.redScore > newInfo.blueScore && newInfo.blueScore <= 0) { + newInfo.shutoutBonus = 100; + } else { + newInfo.shutoutBonus = 0; + } + + newInfo.skillBonus = trap_Cvar_VariableValue("g_spSkill"); + if (newInfo.skillBonus <= 0) { + newInfo.skillBonus = 1; + } + newInfo.score = newInfo.baseScore + newInfo.shutoutBonus + newInfo.timeBonus; + newInfo.score *= newInfo.skillBonus; + + // see if the score is higher for this one + newHigh = (newInfo.redScore > newInfo.blueScore && newInfo.score > oldInfo.score); + + if (newHigh) { + // if so write out the new one + uiInfo.newHighScoreTime = uiInfo.uiDC.realTime + 20000; + if (trap_FS_FOpenFile(fileName, &f, FS_WRITE) >= 0) { + size = sizeof(postGameInfo_t); + trap_FS_Write(&size, sizeof(int), f); + trap_FS_Write(&newInfo, sizeof(postGameInfo_t), f); + trap_FS_FCloseFile(f); + } + } + + if (newInfo.time < oldInfo.time) { + uiInfo.newBestTime = uiInfo.uiDC.realTime + 20000; + } + + // put back all the ui overrides + trap_Cvar_Set("capturelimit", UI_Cvar_VariableString("ui_saveCaptureLimit")); + trap_Cvar_Set("fraglimit", UI_Cvar_VariableString("ui_saveFragLimit")); + trap_Cvar_Set("duel_fraglimit", UI_Cvar_VariableString("ui_saveDuelLimit")); + trap_Cvar_Set("cg_drawTimer", UI_Cvar_VariableString("ui_drawTimer")); + trap_Cvar_Set("g_doWarmup", UI_Cvar_VariableString("ui_doWarmup")); + trap_Cvar_Set("g_Warmup", UI_Cvar_VariableString("ui_Warmup")); + trap_Cvar_Set("sv_pure", UI_Cvar_VariableString("ui_pure")); + trap_Cvar_Set("g_friendlyFire", UI_Cvar_VariableString("ui_friendlyFire")); + + UI_SetBestScores(&newInfo, qtrue); + UI_ShowPostGame(newHigh); + + +} + + +/* +================= +UI_ConsoleCommand +================= +*/ +qboolean UI_ConsoleCommand( int realTime ) { + char *cmd; + + uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime; + uiInfo.uiDC.realTime = realTime; + + cmd = UI_Argv( 0 ); + + // ensure minimum menu data is available + //Menu_Cache(); + + if ( Q_stricmp (cmd, "ui_test") == 0 ) { + UI_ShowPostGame(qtrue); + } + + if ( Q_stricmp (cmd, "ui_report") == 0 ) { + UI_Report(); + return qtrue; + } + + if ( Q_stricmp (cmd, "ui_load") == 0 ) { + UI_Load(); + return qtrue; + } + + if ( Q_stricmp (cmd, "ui_opensiegemenu" ) == 0 ) + { + if ( trap_Cvar_VariableValue ( "g_gametype" ) == GT_SIEGE ) + { + Menus_CloseAll(); + if (Menus_ActivateByName(UI_Argv(1))) + { + trap_Key_SetCatcher( KEYCATCH_UI ); + } + } + return qtrue; + } + + if ( Q_stricmp (cmd, "ui_openmenu" ) == 0 ) + { + //if ( trap_Cvar_VariableValue ( "developer" ) ) + { + Menus_CloseAll(); + if (Menus_ActivateByName(UI_Argv(1))) + { + trap_Key_SetCatcher( KEYCATCH_UI ); + } + return qtrue; + } + } + + /* + if ( Q_stricmp (cmd, "remapShader") == 0 ) { + if (trap_Argc() == 4) { + char shader1[MAX_QPATH]; + char shader2[MAX_QPATH]; + Q_strncpyz(shader1, UI_Argv(1), sizeof(shader1)); + Q_strncpyz(shader2, UI_Argv(2), sizeof(shader2)); + trap_R_RemapShader(shader1, shader2, UI_Argv(3)); + return qtrue; + } + } + */ + + if ( Q_stricmp (cmd, "postgame") == 0 ) { + UI_CalcPostGameStats(); + return qtrue; + } + + if ( Q_stricmp (cmd, "ui_cache") == 0 ) { + UI_Cache_f(); + return qtrue; + } + + if ( Q_stricmp (cmd, "ui_teamOrders") == 0 ) { + //UI_TeamOrdersMenu_f(); + return qtrue; + } + + + if ( Q_stricmp (cmd, "ui_cdkey") == 0 ) { + //UI_CDKeyMenu_f(); + return qtrue; + } + + return qfalse; +} + +/* +================= +UI_Shutdown +================= +*/ +void UI_Shutdown( void ) { +} + + +void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { + qhandle_t hShader; + + hShader = trap_R_RegisterShaderNoMip( picname ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +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 vertical + w = -w; + s0 = 1; + s1 = 0; + } + else { + s0 = 0; + s1 = 1; + } + + if( h < 0 ) { // flip about horizontal + h = -h; + t0 = 1; + t1 = 0; + } + else { + t0 = 0; + t1 = 1; + } + + trap_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 ) { + trap_R_SetColor( color ); + + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + + trap_R_SetColor( NULL ); +} + +void UI_DrawSides(float x, float y, float w, float h) { + trap_R_DrawStretchPic( x, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x + w - 1, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); +} + +void UI_DrawTopBottom(float x, float y, float w, float h) { + trap_R_DrawStretchPic( x, y, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader ); + trap_R_DrawStretchPic( x, y + h - 1, w, 1, 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, const float *color ) { + trap_R_SetColor( color ); + + UI_DrawTopBottom(x, y, width, height); + UI_DrawSides(x, y, width, height); + + trap_R_SetColor( NULL ); +} + +void UI_SetColor( const float *rgba ) { + trap_R_SetColor( rgba ); +} + +void UI_UpdateScreen( void ) { + trap_UpdateScreen(); +} + + +void UI_DrawTextBox (int x, int y, int width, int lines) +{ + UI_FillRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorBlack ); + UI_DrawRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorWhite ); +} + +qboolean UI_CursorInRect (int x, int y, int width, int height) +{ + if (uiInfo.uiDC.cursorx < x || + uiInfo.uiDC.cursory < y || + uiInfo.uiDC.cursorx > x+width || + uiInfo.uiDC.cursory > y+height) + return qfalse; + + return qtrue; +} diff --git a/codemp/ui/ui_force.c b/codemp/ui/ui_force.c new file mode 100644 index 0000000..dc5e265 --- /dev/null +++ b/codemp/ui/ui_force.c @@ -0,0 +1,1345 @@ +// +/* +======================================================================= + +FORCE INTERFACE + +======================================================================= +*/ + +// use this to get a demo build without an explicit demo build, i.e. to get the demo ui files to build +#include "ui_local.h" +#include "../qcommon/qfiles.h" +#include "ui_force.h" + +int uiForceSide = FORCE_LIGHTSIDE; +int uiJediNonJedi = -1; +int uiForceRank = FORCE_MASTERY_JEDI_KNIGHT; +int uiMaxRank = MAX_FORCE_RANK; +int uiMaxPoints = 20; +int uiForceUsed = 0; +int uiForceAvailable=0; + +extern const char *UI_TeamName(int team); + +qboolean gTouchedForce = qfalse; +vmCvar_t ui_freeSaber, ui_forcePowerDisable; + +#include "../namespace_begin.h" +void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow); +#include "../namespace_end.h" + +qboolean uiForcePowersDisabled[NUM_FORCE_POWERS] = { + qfalse,//FP_HEAL,//instant + qfalse,//FP_LEVITATION,//hold/duration + qfalse,//FP_SPEED,//duration + qfalse,//FP_PUSH,//hold/duration + qfalse,//FP_PULL,//hold/duration + qfalse,//FP_TELEPATHY,//instant + qfalse,//FP_GRIP,//hold/duration + qfalse,//FP_LIGHTNING,//hold/duration + qfalse,//FP_RAGE,//duration + qfalse,//FP_PROTECT, + qfalse,//FP_ABSORB, + qfalse,//FP_TEAM_HEAL, + qfalse,//FP_TEAM_FORCE, + qfalse,//FP_DRAIN, + qfalse,//FP_SEE, + qfalse,//FP_SABER_OFFENSE, + qfalse,//FP_SABER_DEFENSE, + qfalse//FP_SABERTHROW, +}; + +int uiForcePowersRank[NUM_FORCE_POWERS] = { + 0,//FP_HEAL = 0,//instant + 1,//FP_LEVITATION,//hold/duration, this one defaults to 1 (gives a free point) + 0,//FP_SPEED,//duration + 0,//FP_PUSH,//hold/duration + 0,//FP_PULL,//hold/duration + 0,//FP_TELEPATHY,//instant + 0,//FP_GRIP,//hold/duration + 0,//FP_LIGHTNING,//hold/duration + 0,//FP_RAGE,//duration + 0,//FP_PROTECT, + 0,//FP_ABSORB, + 0,//FP_TEAM_HEAL, + 0,//FP_TEAM_FORCE, + 0,//FP_DRAIN, + 0,//FP_SEE, + 1,//FP_SABER_OFFENSE, //default to 1 point in attack + 1,//FP_SABER_DEFENSE, //defualt to 1 point in defense + 0//FP_SABERTHROW, +}; + +int uiForcePowerDarkLight[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 uiForceStarShaders[NUM_FORCE_STAR_IMAGES][2]; +int uiSaberColorShaders[NUM_SABER_COLORS]; +void UI_InitForceShaders(void) +{ + uiForceStarShaders[0][0] = trap_R_RegisterShaderNoMip("forcestar0"); + uiForceStarShaders[0][1] = trap_R_RegisterShaderNoMip("forcestar0"); + uiForceStarShaders[1][0] = trap_R_RegisterShaderNoMip("forcecircle1"); + uiForceStarShaders[1][1] = trap_R_RegisterShaderNoMip("forcestar1"); + uiForceStarShaders[2][0] = trap_R_RegisterShaderNoMip("forcecircle2"); + uiForceStarShaders[2][1] = trap_R_RegisterShaderNoMip("forcestar2"); + uiForceStarShaders[3][0] = trap_R_RegisterShaderNoMip("forcecircle3"); + uiForceStarShaders[3][1] = trap_R_RegisterShaderNoMip("forcestar3"); + uiForceStarShaders[4][0] = trap_R_RegisterShaderNoMip("forcecircle4"); + uiForceStarShaders[4][1] = trap_R_RegisterShaderNoMip("forcestar4"); + uiForceStarShaders[5][0] = trap_R_RegisterShaderNoMip("forcecircle5"); + uiForceStarShaders[5][1] = trap_R_RegisterShaderNoMip("forcestar5"); + uiForceStarShaders[6][0] = trap_R_RegisterShaderNoMip("forcecircle6"); + uiForceStarShaders[6][1] = trap_R_RegisterShaderNoMip("forcestar6"); + uiForceStarShaders[7][0] = trap_R_RegisterShaderNoMip("forcecircle7"); + uiForceStarShaders[7][1] = trap_R_RegisterShaderNoMip("forcestar7"); + uiForceStarShaders[8][0] = trap_R_RegisterShaderNoMip("forcecircle8"); + uiForceStarShaders[8][1] = trap_R_RegisterShaderNoMip("forcestar8"); + + uiSaberColorShaders[SABER_RED] = trap_R_RegisterShaderNoMip("menu/art/saber_red"); + uiSaberColorShaders[SABER_ORANGE] = trap_R_RegisterShaderNoMip("menu/art/saber_orange"); + uiSaberColorShaders[SABER_YELLOW] = trap_R_RegisterShaderNoMip("menu/art/saber_yellow"); + uiSaberColorShaders[SABER_GREEN] = trap_R_RegisterShaderNoMip("menu/art/saber_green"); + uiSaberColorShaders[SABER_BLUE] = trap_R_RegisterShaderNoMip("menu/art/saber_blue"); + uiSaberColorShaders[SABER_PURPLE] = trap_R_RegisterShaderNoMip("menu/art/saber_purple"); +} + +// Draw the stars spent on the current force power +void UI_DrawForceStars(rectDef_t *rect, float scale, vec4_t color, int textStyle, int forceindex, int val, int min, int max) +{ + int i,pad = 4; + int xPos,width = 16; + int starcolor; + + if (val < min || val > max) + { + val = min; + } + + if (1) // if (val) + { + xPos = rect->x; + + for (i=FORCE_LEVEL_1;i<=max;i++) + { + starcolor = bgForcePowerCost[forceindex][i]; + + if (uiForcePowersDisabled[forceindex]) + { + vec4_t grColor = {0.2f, 0.2f, 0.2f, 1.0f}; + trap_R_SetColor(grColor); + } + + if (val >= i) + { // Draw a star. + UI_DrawHandlePic( xPos, rect->y+6, width, width, uiForceStarShaders[starcolor][1] ); + } + else + { // Draw a circle. + UI_DrawHandlePic( xPos, rect->y+6, width, width, uiForceStarShaders[starcolor][0] ); + } + + if (uiForcePowersDisabled[forceindex]) + { + trap_R_SetColor(NULL); + } + + xPos += width + pad; + } + } +} + +// Set the client's force power layout. +void UI_UpdateClientForcePowers(const char *teamArg) +{ + trap_Cvar_Set( "forcepowers", va("%i-%i-%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i", + uiForceRank, uiForceSide, uiForcePowersRank[0], uiForcePowersRank[1], + uiForcePowersRank[2], uiForcePowersRank[3], uiForcePowersRank[4], + uiForcePowersRank[5], uiForcePowersRank[6], uiForcePowersRank[7], + uiForcePowersRank[8], uiForcePowersRank[9], uiForcePowersRank[10], + uiForcePowersRank[11], uiForcePowersRank[12], uiForcePowersRank[13], + uiForcePowersRank[14], uiForcePowersRank[15], uiForcePowersRank[16], + uiForcePowersRank[17]) ); + + if (gTouchedForce) + { + if (teamArg && teamArg[0]) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va("forcechanged \"%s\"\n", teamArg) ); + } + else + { + trap_Cmd_ExecuteText( EXEC_APPEND, "forcechanged\n" ); + } + } + + gTouchedForce = qfalse; +} + +int UI_TranslateFCFIndex(int index) +{ + if (uiForceSide == FORCE_LIGHTSIDE) + { + return index-uiInfo.forceConfigLightIndexBegin; + } + + return index-uiInfo.forceConfigDarkIndexBegin; +} + +void UI_SaveForceTemplate() +{ + char *selectedName = UI_Cvar_VariableString("ui_SaveFCF"); + char fcfString[512]; + char forceStringValue[4]; + fileHandle_t f; + int strPlace = 0; + int forcePlace = 0; + int i = 0; + qboolean foundFeederItem = qfalse; + + if (!selectedName || !selectedName[0]) + { + Com_Printf("You did not provide a name for the template.\n"); + return; + } + + if (uiForceSide == FORCE_LIGHTSIDE) + { //write it into the light side folder + trap_FS_FOpenFile(va("forcecfg/light/%s.fcf", selectedName), &f, FS_WRITE); + } + else + { //if it isn't light it must be dark + trap_FS_FOpenFile(va("forcecfg/dark/%s.fcf", selectedName), &f, FS_WRITE); + } + + if (!f) + { + Com_Printf("There was an error writing the template file (read-only?).\n"); + return; + } + + Com_sprintf(fcfString, sizeof(fcfString), "%i-%i-", uiForceRank, uiForceSide); + strPlace = strlen(fcfString); + + while (forcePlace < NUM_FORCE_POWERS) + { + Com_sprintf(forceStringValue, sizeof(forceStringValue), "%i", uiForcePowersRank[forcePlace]); + //Just use the force digit even if multiple digits. Shouldn't be longer than 1. + fcfString[strPlace] = forceStringValue[0]; + strPlace++; + forcePlace++; + } + fcfString[strPlace] = '\n'; + fcfString[strPlace+1] = 0; + + trap_FS_Write(fcfString, strlen(fcfString), f); + trap_FS_FCloseFile(f); + + Com_Printf("Template saved as \"%s\".\n", selectedName); + + //Now, update the FCF list + UI_LoadForceConfig_List(); + + //Then, scroll through and select the template for the file we just saved + while (i < uiInfo.forceConfigCount) + { + if (!Q_stricmp(uiInfo.forceConfigNames[i], selectedName)) + { + if ((uiForceSide == FORCE_LIGHTSIDE && uiInfo.forceConfigSide[i]) || + (uiForceSide == FORCE_DARKSIDE && !uiInfo.forceConfigSide[i])) + { + Menu_SetFeederSelection(NULL, FEEDER_FORCECFG, UI_TranslateFCFIndex(i), NULL); + foundFeederItem = qtrue; + } + } + + i++; + } + + //Else, go back to 0 + if (!foundFeederItem) + { + Menu_SetFeederSelection(NULL, FEEDER_FORCECFG, 0, NULL); + } +} + + +// +extern qboolean UI_TrueJediEnabled( void ); +void UpdateForceUsed() +{ + int curpower, currank; + menuDef_t *menu; + + // Currently we don't make a distinction between those that wish to play Jedi of lower than maximum skill. + uiForceRank = uiMaxRank; + + uiForceUsed = 0; + uiForceAvailable = forceMasteryPoints[uiForceRank]; + + // Make sure that we have one freebie in jump. + if (uiForcePowersRank[FP_LEVITATION]<1) + { + uiForcePowersRank[FP_LEVITATION]=1; + } + + if ( UI_TrueJediEnabled() ) + {//true jedi mode is set + if ( uiJediNonJedi == -1 ) + { + int x = 0; + qboolean clear = qfalse, update = qfalse; + uiJediNonJedi = FORCE_NONJEDI; + while ( x < NUM_FORCE_POWERS ) + {//if any force power is set, we must be a jedi + if ( x == FP_LEVITATION || x == FP_SABER_OFFENSE ) + { + if ( uiForcePowersRank[x] > 1 ) + { + uiJediNonJedi = FORCE_JEDI; + break; + } + else if ( uiForcePowersRank[x] > 0 ) + { + clear = qtrue; + } + } + else if ( uiForcePowersRank[x] > 0 ) + { + uiJediNonJedi = FORCE_JEDI; + break; + } + x++; + } + if ( uiJediNonJedi == FORCE_JEDI ) + { + if ( uiForcePowersRank[FP_SABER_OFFENSE] < 1 ) + { + uiForcePowersRank[FP_SABER_OFFENSE]=1; + update = qtrue; + } + } + else if ( clear ) + { + x = 0; + while ( x < NUM_FORCE_POWERS ) + {//clear all force + uiForcePowersRank[x] = 0; + x++; + } + update = qtrue; + } + if ( update ) + { + int myTeam; + myTeam = (int)(trap_Cvar_VariableValue("ui_myteam")); + if ( myTeam != TEAM_SPECTATOR ) + { + UI_UpdateClientForcePowers(UI_TeamName(myTeam));//will cause him to respawn, if it's been 5 seconds since last one + } + else + { + UI_UpdateClientForcePowers(NULL);//just update powers + } + } + } + } + + menu = Menus_FindByName("ingame_playerforce"); + // Set the cost of the saberattack according to whether its free. + if (ui_freeSaber.integer) + { // Make saber free + bgForcePowerCost[FP_SABER_OFFENSE][FORCE_LEVEL_1] = 0; + bgForcePowerCost[FP_SABER_DEFENSE][FORCE_LEVEL_1] = 0; + // Make sure that we have one freebie in saber if applicable. + if (uiForcePowersRank[FP_SABER_OFFENSE]<1) + { + uiForcePowersRank[FP_SABER_OFFENSE]=1; + } + if (uiForcePowersRank[FP_SABER_DEFENSE]<1) + { + uiForcePowersRank[FP_SABER_DEFENSE]=1; + } + if (menu) + { + Menu_ShowItemByName(menu, "setFP_SABER_DEFENSE", qtrue); + Menu_ShowItemByName(menu, "setfp_saberthrow", qtrue); + Menu_ShowItemByName(menu, "effectentry", qtrue); + Menu_ShowItemByName(menu, "effectfield", qtrue); + Menu_ShowItemByName(menu, "nosaber", qfalse); + } + } + else + { // Make saber normal cost + bgForcePowerCost[FP_SABER_OFFENSE][FORCE_LEVEL_1] = 1; + bgForcePowerCost[FP_SABER_DEFENSE][FORCE_LEVEL_1] = 1; + // Also, check if there is no saberattack. If there isn't, there had better not be any defense or throw! + if (uiForcePowersRank[FP_SABER_OFFENSE]<1) + { + uiForcePowersRank[FP_SABER_DEFENSE]=0; + uiForcePowersRank[FP_SABERTHROW]=0; + if (menu) + { + Menu_ShowItemByName(menu, "setfp_saberdefend", qfalse); + Menu_ShowItemByName(menu, "setfp_saberthrow", qfalse); + Menu_ShowItemByName(menu, "effectentry", qfalse); + Menu_ShowItemByName(menu, "effectfield", qfalse); + Menu_ShowItemByName(menu, "nosaber", qtrue); + } + } + else + { + if (menu) + { + Menu_ShowItemByName(menu, "setfp_saberdefend", qtrue); + Menu_ShowItemByName(menu, "setfp_saberthrow", qtrue); + Menu_ShowItemByName(menu, "effectentry", qtrue); + Menu_ShowItemByName(menu, "effectfield", qtrue); + Menu_ShowItemByName(menu, "nosaber", qfalse); + } + } + } + + // Make sure that we're still legal. + for (curpower=0;curpower=NUM_FORCE_POWER_LEVELS) + uiForcePowersRank[curpower]=(NUM_FORCE_POWER_LEVELS-1); + + for (currank=FORCE_LEVEL_1;currank<=uiForcePowersRank[curpower];currank++) + { // Check on this force power + if (uiForcePowersRank[curpower]>0) + { // Do not charge the player for the one freebie in jump, or if there is one in saber. + if ( (curpower == FP_LEVITATION && currank == FORCE_LEVEL_1) || + (curpower == FP_SABER_OFFENSE && currank == FORCE_LEVEL_1 && ui_freeSaber.integer) || + (curpower == FP_SABER_DEFENSE && currank == FORCE_LEVEL_1 && ui_freeSaber.integer) ) + { + // Do nothing (written this way for clarity) + } + else + { // Check if we can accrue the cost of this power. + if (bgForcePowerCost[curpower][currank] > uiForceAvailable) + { // We can't afford this power. Break to the next one. + // Remove this power from the player's roster. + uiForcePowersRank[curpower] = currank-1; + break; + } + else + { // Sure we can afford it. + uiForceUsed += bgForcePowerCost[curpower][currank]; + uiForceAvailable -= bgForcePowerCost[curpower][currank]; + } + } + } + } + } + +} + + +//Mostly parts of other functions merged into one another. +//Puts the current UI stuff into a string, legalizes it, and then reads it back out. +void UI_ReadLegalForce(void) +{ + char fcfString[512]; + char forceStringValue[4]; + int strPlace = 0; + int forcePlace = 0; + int i = 0; + char singleBuf[64]; + char info[MAX_INFO_VALUE]; + int c = 0; + int iBuf = 0; + int forcePowerRank = 0; + int currank = 0; + int forceTeam = 0; + qboolean updateForceLater = qfalse; + + //First, stick them into a string. + Com_sprintf(fcfString, sizeof(fcfString), "%i-%i-", uiForceRank, uiForceSide); + strPlace = strlen(fcfString); + + while (forcePlace < NUM_FORCE_POWERS) + { + Com_sprintf(forceStringValue, sizeof(forceStringValue), "%i", uiForcePowersRank[forcePlace]); + //Just use the force digit even if multiple digits. Shouldn't be longer than 1. + fcfString[strPlace] = forceStringValue[0]; + strPlace++; + forcePlace++; + } + fcfString[strPlace] = '\n'; + fcfString[strPlace+1] = 0; + + info[0] = '\0'; + trap_GetConfigString(CS_SERVERINFO, info, sizeof(info)); + + if (atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) )) + { + switch((int)(trap_Cvar_VariableValue("ui_myteam"))) + { + case TEAM_RED: + forceTeam = FORCE_DARKSIDE; + break; + case TEAM_BLUE: + forceTeam = FORCE_LIGHTSIDE; + break; + default: + break; + } + } + //Second, legalize them. + if (!BG_LegalizedForcePowers(fcfString, uiMaxRank, ui_freeSaber.integer, forceTeam, atoi( Info_ValueForKey( info, "g_gametype" )), 0)) + { //if they were illegal, we should refresh them. + updateForceLater = qtrue; + } + + //Lastly, put them back into the UI storage from the legalized string + i = 0; + + while (fcfString[i] && fcfString[i] != '-') + { + singleBuf[c] = fcfString[i]; + c++; + i++; + } + singleBuf[c] = 0; + c = 0; + i++; + + iBuf = atoi(singleBuf); + + if (iBuf > uiMaxRank || iBuf < 0) + { //this force config uses a rank level higher than our currently restricted level.. so we can't use it + //FIXME: Print a message indicating this to the user + // return; + } + + uiForceRank = iBuf; + + while (fcfString[i] && fcfString[i] != '-') + { + singleBuf[c] = fcfString[i]; + c++; + i++; + } + singleBuf[c] = 0; + c = 0; + i++; + + uiForceSide = atoi(singleBuf); + + if (uiForceSide != FORCE_LIGHTSIDE && + uiForceSide != FORCE_DARKSIDE) + { + uiForceSide = FORCE_LIGHTSIDE; + return; + } + + //clear out the existing powers + while (c < NUM_FORCE_POWERS) + { + uiForcePowersRank[c] = 0; + c++; + } + uiForceUsed = 0; + uiForceAvailable = forceMasteryPoints[uiForceRank]; + gTouchedForce = qtrue; + + for (c=0;fcfString[i]&&c FORCE_LEVEL_3 || forcePowerRank < 0) + { //err.. not correct + continue; // skip this power + } + + if (uiForcePowerDarkLight[c] && uiForcePowerDarkLight[c] != uiForceSide) + { //Apparently the user has crafted a force config that has powers that don't fit with the config's side. + continue; // skip this power + } + + // Accrue cost for each assigned rank for this power. + for (currank=FORCE_LEVEL_1;currank<=forcePowerRank;currank++) + { + if (bgForcePowerCost[c][currank] > uiForceAvailable) + { // Break out, we can't afford any more power. + break; + } + // Pay for this rank of this power. + uiForceUsed += bgForcePowerCost[c][currank]; + uiForceAvailable -= bgForcePowerCost[c][currank]; + + uiForcePowersRank[c]++; + } + } + + if (uiForcePowersRank[FP_LEVITATION] < 1) + { + uiForcePowersRank[FP_LEVITATION]=1; + } + if (uiForcePowersRank[FP_SABER_OFFENSE] < 1 && ui_freeSaber.integer) + { + uiForcePowersRank[FP_SABER_OFFENSE]=1; + } + if (uiForcePowersRank[FP_SABER_DEFENSE] < 1 && ui_freeSaber.integer) + { + uiForcePowersRank[FP_SABER_DEFENSE]=1; + } + + UpdateForceUsed(); + + if (updateForceLater) + { + gTouchedForce = qtrue; + UI_UpdateClientForcePowers(NULL); + } +} + +void UI_UpdateForcePowers() +{ + char *forcePowers = UI_Cvar_VariableString("forcepowers"); + char readBuf[256]; + int i = 0, i_f = 0, i_r = 0; + + uiForceSide = 0; + + if (forcePowers && forcePowers[0]) + { + while (forcePowers[i]) + { + i_r = 0; + + while (forcePowers[i] && forcePowers[i] != '-' && i_r < 255) + { + readBuf[i_r] = forcePowers[i]; + i_r++; + i++; + } + readBuf[i_r] = '\0'; + if (i_r >= 255 || !forcePowers[i] || forcePowers[i] != '-') + { + uiForceSide = 0; + goto validitycheck; + } + uiForceRank = atoi(readBuf); + i_r = 0; + + if (uiForceRank > uiMaxRank) + { + uiForceRank = uiMaxRank; + } + + i++; + + while (forcePowers[i] && forcePowers[i] != '-' && i_r < 255) + { + readBuf[i_r] = forcePowers[i]; + i_r++; + i++; + } + readBuf[i_r] = '\0'; + if (i_r >= 255 || !forcePowers[i] || forcePowers[i] != '-') + { + uiForceSide = 0; + goto validitycheck; + } + uiForceSide = atoi(readBuf); + i_r = 0; + + i++; + + i_f = FP_HEAL; + + while (forcePowers[i] && i_f < NUM_FORCE_POWERS) + { + readBuf[0] = forcePowers[i]; + readBuf[1] = '\0'; + uiForcePowersRank[i_f] = atoi(readBuf); + + if (i_f == FP_LEVITATION && + uiForcePowersRank[i_f] < 1) + { + uiForcePowersRank[i_f] = 1; + } + + if (i_f == FP_SABER_OFFENSE && + uiForcePowersRank[i_f] < 1 && + ui_freeSaber.integer) + { + uiForcePowersRank[i_f] = 1; + } + + if (i_f == FP_SABER_DEFENSE && + uiForcePowersRank[i_f] < 1 && + ui_freeSaber.integer) + { + uiForcePowersRank[i_f] = 1; + } + + i_f++; + i++; + } + + if (i_f < NUM_FORCE_POWERS) + { //info for all the powers wasn't there.. + uiForceSide = 0; + goto validitycheck; + } + i++; + } + } + +validitycheck: + + if (!uiForceSide) + { + uiForceSide = 1; + uiForceRank = 1; + i = 0; + while (i < NUM_FORCE_POWERS) + { + if (i == FP_LEVITATION) + { + uiForcePowersRank[i] = 1; + } + else if (i == FP_SABER_OFFENSE && ui_freeSaber.integer) + { + uiForcePowersRank[i] = 1; + } + else if (i == FP_SABER_DEFENSE && ui_freeSaber.integer) + { + uiForcePowersRank[i] = 1; + } + else + { + uiForcePowersRank[i] = 0; + } + + i++; + } + + UI_UpdateClientForcePowers(NULL); + } + + UpdateForceUsed(); +} +extern int uiSkinColor; +extern int uiHoldSkinColor; + +qboolean UI_SkinColor_HandleKey(int flags, float *special, int key, int num, int min, int max, int type) +{ + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) + { + int i = num; + + if (key == A_MOUSE2) + { + i--; + } + else + { + i++; + } + + if (i < min) + { + i = max; + } + else if (i > max) + { + i = min; + } + + num = i; + + uiSkinColor = num; + + uiHoldSkinColor = uiSkinColor; + + UI_FeederSelection(FEEDER_Q3HEADS, uiInfo.q3SelectedHead, NULL); + + return qtrue; + } + return qfalse; +} + + + + +qboolean UI_ForceSide_HandleKey(int flags, float *special, int key, int num, int min, int max, int type) +{ + char info[MAX_INFO_VALUE]; + + info[0] = '\0'; + trap_GetConfigString(CS_SERVERINFO, info, sizeof(info)); + + if (atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) )) + { + switch((int)(trap_Cvar_VariableValue("ui_myteam"))) + { + case TEAM_RED: + return qfalse; + case TEAM_BLUE: + return qfalse; + default: + break; + } + } + + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) + { + int i = num; + int x = 0; + + //update the feeder item selection, it might be different depending on side + Menu_SetFeederSelection(NULL, FEEDER_FORCECFG, 0, NULL); + + if (key == A_MOUSE2) + { + i--; + } + else + { + i++; + } + + if (i < min) + { + i = max; + } + else if (i > max) + { + i = min; + } + + num = i; + + uiForceSide = num; + + // Resetting power ranks based on if light or dark side is chosen + while (x < NUM_FORCE_POWERS) + { + if (uiForcePowerDarkLight[x] && uiForceSide != uiForcePowerDarkLight[x]) + { + uiForcePowersRank[x] = 0; + } + x++; + } + + UpdateForceUsed(); + + gTouchedForce = qtrue; + return qtrue; + } + return qfalse; +} + +qboolean UI_JediNonJedi_HandleKey(int flags, float *special, int key, int num, int min, int max, int type) +{ + char info[MAX_INFO_VALUE]; + + info[0] = '\0'; + trap_GetConfigString(CS_SERVERINFO, info, sizeof(info)); + + if ( !UI_TrueJediEnabled() ) + {//true jedi mode is not set + return qfalse; + } + + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) + { + int i = num; + int x = 0; + + if (key == A_MOUSE2) + { + i--; + } + else + { + i++; + } + + if (i < min) + { + i = max; + } + else if (i > max) + { + i = min; + } + + num = i; + + uiJediNonJedi = num; + + // Resetting power ranks based on if light or dark side is chosen + if ( !num ) + {//not a jedi? + int myTeam = (int)(trap_Cvar_VariableValue("ui_myteam")); + while ( x < NUM_FORCE_POWERS ) + {//clear all force powers + uiForcePowersRank[x] = 0; + x++; + } + if ( myTeam != TEAM_SPECTATOR ) + { + UI_UpdateClientForcePowers(UI_TeamName(myTeam));//will cause him to respawn, if it's been 5 seconds since last one + } + else + { + UI_UpdateClientForcePowers(NULL);//just update powers + } + } + else if ( num ) + {//a jedi, set the minimums, hopefuly they know to set the rest! + if ( uiForcePowersRank[FP_LEVITATION] < FORCE_LEVEL_1 ) + {//force jump 1 minimum + uiForcePowersRank[FP_LEVITATION] = FORCE_LEVEL_1; + } + if ( uiForcePowersRank[FP_SABER_OFFENSE] < FORCE_LEVEL_1 ) + {//saber attack 1, minimum + uiForcePowersRank[FP_SABER_OFFENSE] = FORCE_LEVEL_1; + } + } + + UpdateForceUsed(); + + gTouchedForce = qtrue; + return qtrue; + } + return qfalse; +} + +qboolean UI_ForceMaxRank_HandleKey(int flags, float *special, int key, int num, int min, int max, int type) +{ + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) + { + int i = num; + + if (key == A_MOUSE2) + { + i--; + } + else + { + i++; + } + + if (i < min) + { + i = max; + } + else if (i > max) + { + i = min; + } + + num = i; + + uiMaxRank = num; + + trap_Cvar_Set( "g_maxForceRank", va("%i", num)); + + // The update force used will remove overallocated powers automatically. + UpdateForceUsed(); + + gTouchedForce = qtrue; + + return qtrue; + } + return qfalse; +} + + +// This function will either raise or lower a power by one rank. +qboolean UI_ForcePowerRank_HandleKey(int flags, float *special, int key, int num, int min, int max, int type) +{ + qboolean raising; + + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER || key == A_BACKSPACE) + { + int forcepower, rank; + + //this will give us the index as long as UI_FORCE_RANK is always one below the first force rank index + forcepower = (type-UI_FORCE_RANK)-1; + + //the power is disabled on the server + if (uiForcePowersDisabled[forcepower]) + { + return qtrue; + } + + // If we are not on the same side as a power, or if we are not of any rank at all. + if (uiForcePowerDarkLight[forcepower] && uiForceSide != uiForcePowerDarkLight[forcepower]) + { + return qtrue; + } + else if (forcepower == FP_SABER_DEFENSE || forcepower == FP_SABERTHROW) + { // Saberdefend and saberthrow can't be bought if there is no saberattack + if (uiForcePowersRank[FP_SABER_OFFENSE] < 1) + { + return qtrue; + } + } + + if (type == UI_FORCE_RANK_LEVITATION) + { + min += 1; + } + if (type == UI_FORCE_RANK_SABERATTACK && ui_freeSaber.integer) + { + min += 1; + } + if (type == UI_FORCE_RANK_SABERDEFEND && ui_freeSaber.integer) + { + min += 1; + } + + if (key == A_MOUSE2 || key == A_BACKSPACE) + { // Lower a point. + if (uiForcePowersRank[forcepower]<=min) + { + return qtrue; + } + raising = qfalse; + } + else + { // Raise a point. + if (uiForcePowersRank[forcepower]>=max) + { + return qtrue; + } + raising = qtrue; + } + + if (raising) + { // Check if we can accrue the cost of this power. + rank = uiForcePowersRank[forcepower]+1; + if (bgForcePowerCost[forcepower][rank] > uiForceAvailable) + { // We can't afford this power. Abandon ship. + return qtrue; + } + else + { // Sure we can afford it. + uiForceUsed += bgForcePowerCost[forcepower][rank]; + uiForceAvailable -= bgForcePowerCost[forcepower][rank]; + uiForcePowersRank[forcepower]=rank; + } + } + else + { // Lower the point. + rank = uiForcePowersRank[forcepower]; + uiForceUsed -= bgForcePowerCost[forcepower][rank]; + uiForceAvailable += bgForcePowerCost[forcepower][rank]; + uiForcePowersRank[forcepower]--; + } + + UpdateForceUsed(); + + gTouchedForce = qtrue; + + return qtrue; + } + return qfalse; +} + + +int gCustRank = 0; +int gCustSide = 0; + +int gCustPowersRank[NUM_FORCE_POWERS] = { + 0,//FP_HEAL = 0,//instant + 1,//FP_LEVITATION,//hold/duration, this one defaults to 1 (gives a free point) + 0,//FP_SPEED,//duration + 0,//FP_PUSH,//hold/duration + 0,//FP_PULL,//hold/duration + 0,//FP_TELEPATHY,//instant + 0,//FP_GRIP,//hold/duration + 0,//FP_LIGHTNING,//hold/duration + 0,//FP_RAGE,//duration + 0,//FP_PROTECT, + 0,//FP_ABSORB, + 0,//FP_TEAM_HEAL, + 0,//FP_TEAM_FORCE, + 0,//FP_DRAIN, + 0,//FP_SEE, + 0,//FP_SABER_OFFENSE, + 0,//FP_SABER_DEFENSE, + 0//FP_SABERTHROW, +}; + +/* +================= +UI_ForceConfigHandle +================= +*/ +void UI_ForceConfigHandle( int oldindex, int newindex ) +{ + fileHandle_t f; + int len = 0; + int i = 0; + int c = 0; + int iBuf = 0, forcePowerRank, currank; + char fcfBuffer[8192]; + char singleBuf[64]; + char info[MAX_INFO_VALUE]; + int forceTeam = 0; + + if (oldindex == 0) + { //switching out from custom config, so first shove the current values into the custom storage + i = 0; + + while (i < NUM_FORCE_POWERS) + { + gCustPowersRank[i] = uiForcePowersRank[i]; + i++; + } + gCustRank = uiForceRank; + gCustSide = uiForceSide; + } + + if (newindex == 0) + { //switching back to custom, shove the values back in from the custom storage + i = 0; + uiForceUsed = 0; + gTouchedForce = qtrue; + + while (i < NUM_FORCE_POWERS) + { + uiForcePowersRank[i] = gCustPowersRank[i]; + uiForceUsed += uiForcePowersRank[i]; + i++; + } + uiForceRank = gCustRank; + uiForceSide = gCustSide; + + UpdateForceUsed(); + return; + } + + //If we made it here, we want to load in a new config + if (uiForceSide == FORCE_LIGHTSIDE) + { //we should only be displaying lightside configs, so.. look in the light folder + newindex += uiInfo.forceConfigLightIndexBegin; + if (newindex >= uiInfo.forceConfigCount) + { + return; + } + len = trap_FS_FOpenFile(va("forcecfg/light/%s.fcf", uiInfo.forceConfigNames[newindex]), &f, FS_READ); + } + else + { //else dark + newindex += uiInfo.forceConfigDarkIndexBegin; + if (newindex >= uiInfo.forceConfigCount || newindex > uiInfo.forceConfigLightIndexBegin) + { //dark gets read in before light + return; + } + len = trap_FS_FOpenFile(va("forcecfg/dark/%s.fcf", uiInfo.forceConfigNames[newindex]), &f, FS_READ); + } + + if (len <= 0) + { //This should not have happened. But, before we quit out, attempt searching the other light/dark folder for the file. + if (uiForceSide == FORCE_LIGHTSIDE) + { + len = trap_FS_FOpenFile(va("forcecfg/dark/%s.fcf", uiInfo.forceConfigNames[newindex]), &f, FS_READ); + } + else + { + len = trap_FS_FOpenFile(va("forcecfg/light/%s.fcf", uiInfo.forceConfigNames[newindex]), &f, FS_READ); + } + + if (len <= 0) + { //still failure? Oh well. + return; + } + } + + if (len >= 8192) + { + return; + } + + trap_FS_Read(fcfBuffer, len, f); + fcfBuffer[len] = 0; + trap_FS_FCloseFile(f); + + i = 0; + + info[0] = '\0'; + trap_GetConfigString(CS_SERVERINFO, info, sizeof(info)); + + if (atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) )) + { + switch((int)(trap_Cvar_VariableValue("ui_myteam"))) + { + case TEAM_RED: + forceTeam = FORCE_DARKSIDE; + break; + case TEAM_BLUE: + forceTeam = FORCE_LIGHTSIDE; + break; + default: + break; + } + } + + BG_LegalizedForcePowers(fcfBuffer, uiMaxRank, ui_freeSaber.integer, forceTeam, atoi( Info_ValueForKey( info, "g_gametype" )), 0); + //legalize the config based on the max rank + + //now that we're done with the handle, it's time to parse our force data out of the string + //we store strings in rank-side-xxxxxxxxx format (where the x's are individual force power levels) + while (fcfBuffer[i] && fcfBuffer[i] != '-') + { + singleBuf[c] = fcfBuffer[i]; + c++; + i++; + } + singleBuf[c] = 0; + c = 0; + i++; + + iBuf = atoi(singleBuf); + + if (iBuf > uiMaxRank || iBuf < 0) + { //this force config uses a rank level higher than our currently restricted level.. so we can't use it + //FIXME: Print a message indicating this to the user + return; + } + + uiForceRank = iBuf; + + while (fcfBuffer[i] && fcfBuffer[i] != '-') + { + singleBuf[c] = fcfBuffer[i]; + c++; + i++; + } + singleBuf[c] = 0; + c = 0; + i++; + + uiForceSide = atoi(singleBuf); + + if (uiForceSide != FORCE_LIGHTSIDE && + uiForceSide != FORCE_DARKSIDE) + { + uiForceSide = FORCE_LIGHTSIDE; + return; + } + + //clear out the existing powers + while (c < NUM_FORCE_POWERS) + { + /* + if (c==FP_LEVITATION) + { + uiForcePowersRank[c]=1; + } + else if (c==FP_SABER_OFFENSE && ui_freeSaber.integer) + { + uiForcePowersRank[c]=1; + } + else if (c==FP_SABER_DEFENSE && ui_freeSaber.integer) + { + uiForcePowersRank[c]=1; + } + else + { + uiForcePowersRank[c] = 0; + } + */ + //rww - don't need to do these checks. Just trust whatever the saber config says. + uiForcePowersRank[c] = 0; + c++; + } + uiForceUsed = 0; + uiForceAvailable = forceMasteryPoints[uiForceRank]; + gTouchedForce = qtrue; + + for (c=0;fcfBuffer[i]&&c FORCE_LEVEL_3 || forcePowerRank < 0) + { //err.. not correct + continue; // skip this power + } + + if (uiForcePowerDarkLight[c] && uiForcePowerDarkLight[c] != uiForceSide) + { //Apparently the user has crafted a force config that has powers that don't fit with the config's side. + continue; // skip this power + } + + // Accrue cost for each assigned rank for this power. + for (currank=FORCE_LEVEL_1;currank<=forcePowerRank;currank++) + { + if (bgForcePowerCost[c][currank] > uiForceAvailable) + { // Break out, we can't afford any more power. + break; + } + // Pay for this rank of this power. + uiForceUsed += bgForcePowerCost[c][currank]; + uiForceAvailable -= bgForcePowerCost[c][currank]; + + uiForcePowersRank[c]++; + } + } + + if (uiForcePowersRank[FP_LEVITATION] < 1) + { + uiForcePowersRank[FP_LEVITATION]=1; + } + if (uiForcePowersRank[FP_SABER_OFFENSE] < 1 && ui_freeSaber.integer) + { + uiForcePowersRank[FP_SABER_OFFENSE]=1; + } + if (uiForcePowersRank[FP_SABER_DEFENSE] < 1 && ui_freeSaber.integer) + { + uiForcePowersRank[FP_SABER_DEFENSE]=1; + } + + UpdateForceUsed(); +} diff --git a/codemp/ui/ui_force.h b/codemp/ui/ui_force.h new file mode 100644 index 0000000..088721e --- /dev/null +++ b/codemp/ui/ui_force.h @@ -0,0 +1,33 @@ +#include "../qcommon/qfiles.h" + +#define NUM_FORCE_STAR_IMAGES 9 +#define FORCE_NONJEDI 0 +#define FORCE_JEDI 1 + +extern int uiForceSide; +extern int uiJediNonJedi; +extern int uiForceRank; +extern int uiMaxRank; +extern int uiForceUsed; +extern int uiForceAvailable; +extern qboolean gTouchedForce; +extern qboolean uiForcePowersDisabled[NUM_FORCE_POWERS]; +extern int uiForcePowersRank[NUM_FORCE_POWERS]; +extern int uiForcePowerDarkLight[NUM_FORCE_POWERS]; +extern int uiSaberColorShaders[NUM_SABER_COLORS]; +// Dots above or equal to a given rank carry a certain color. +extern vmCvar_t ui_freeSaber, ui_forcePowerDisable; + +void UI_InitForceShaders(void); +void UI_ReadLegalForce(void); +void UI_DrawTotalForceStars(rectDef_t *rect, float scale, vec4_t color, int textStyle); +void UI_DrawForceStars(rectDef_t *rect, float scale, vec4_t color, int textStyle, int findex, int val, int min, int max) ; +void UI_UpdateClientForcePowers(const char *teamArg); +void UI_SaveForceTemplate(); +void UI_UpdateForcePowers(); +qboolean UI_SkinColor_HandleKey(int flags, float *special, int key, int num, int min, int max, int type); +qboolean UI_ForceSide_HandleKey(int flags, float *special, int key, int num, int min, int max, int type); +qboolean UI_JediNonJedi_HandleKey(int flags, float *special, int key, int num, int min, int max, int type); +qboolean UI_ForceMaxRank_HandleKey(int flags, float *special, int key, int num, int min, int max, int type); +qboolean UI_ForcePowerRank_HandleKey(int flags, float *special, int key, int num, int min, int max, int type); +extern void UI_ForceConfigHandle( int oldindex, int newindex ); diff --git a/codemp/ui/ui_gameinfo.c b/codemp/ui/ui_gameinfo.c new file mode 100644 index 0000000..bfcc625 --- /dev/null +++ b/codemp/ui/ui_gameinfo.c @@ -0,0 +1,333 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// +// gameinfo.c +// + +#include "ui_local.h" + + +// +// arena and bot info +// + + +int ui_numBots; +static char *ui_botInfos[MAX_BOTS]; + +static int ui_numArenas; +static char *ui_arenaInfos[MAX_ARENAS]; + +/* +=============== +UI_ParseInfos +=============== +*/ +int UI_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 *) UI_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); + if (infos[count]) { + strcpy(infos[count], info); +#ifndef FINAL_BUILD + if (trap_Cvar_VariableValue("com_buildScript")) + { + char *botFile = Info_ValueForKey(info, "personality"); + if (botFile && botFile[0]) + { + int fh = 0; + trap_FS_FOpenFile(botFile, &fh, FS_READ); + if (fh) + { + trap_FS_FCloseFile(fh); + } + } + } +#endif + count++; + } + } + return count; +} + +/* +=============== +UI_LoadArenasFromFile +=============== +*/ +static void UI_LoadArenasFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + trap_Print( 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 ); + + ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] ); +} + +/* +=============== +UI_LoadArenas +=============== +*/ +void UI_LoadArenas( void ) { + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + char *type; + + ui_numArenas = 0; + uiInfo.mapCount = 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); + UI_LoadArenasFromFile(filename); + } +// trap_Print( va( "%i arenas parsed\n", ui_numArenas ) ); + if (UI_OutOfMemory()) { + trap_Print(S_COLOR_YELLOW"WARNING: not anough memory in pool to load all arenas\n"); + } + + for( n = 0; n < ui_numArenas; n++ ) { + // determine type + + uiInfo.mapList[uiInfo.mapCount].cinematic = -1; + uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "map")); + uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "longname")); + uiInfo.mapList[uiInfo.mapCount].levelShot = -1; + uiInfo.mapList[uiInfo.mapCount].imageName = String_Alloc(va("levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName)); + uiInfo.mapList[uiInfo.mapCount].typeBits = 0; + + type = Info_ValueForKey( ui_arenaInfos[n], "type" ); + // if no type specified, it will be treated as "ffa" + if( *type ) { + if( strstr( type, "ffa" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_FFA); + } + if( strstr( type, "holocron" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_HOLOCRON); + } + if( strstr( type, "jedimaster" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_JEDIMASTER); + } + if( strstr( type, "duel" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_DUEL); + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_POWERDUEL); + } + if( strstr( type, "powerduel" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_DUEL); + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_POWERDUEL); + } + if( strstr( type, "siege" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_SIEGE); + } + if( strstr( type, "ctf" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_CTF); + } + if( strstr( type, "cty" ) ) { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_CTY); + } + } else { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << GT_FFA); + } + + uiInfo.mapCount++; + if (uiInfo.mapCount >= MAX_MAPS) { + break; + } + } +} + + +/* +=============== +UI_LoadBotsFromFile +=============== +*/ +static void UI_LoadBotsFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + char *stopMark; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + trap_Print( 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; + + stopMark = strstr(buf, "@STOPHERE"); + + //This bot is in place as a mark for modview's bot viewer. + //If we hit it just stop and trace back to the beginning of the bot define and cut the string off. + //This is only done in the UI and not the game so that "test" bots can be added manually and still + //not show up in the menu. + if (stopMark) + { + int startPoint = stopMark - buf; + + while (buf[startPoint] != '{') + { + startPoint--; + } + + buf[startPoint] = 0; + } + + trap_FS_FCloseFile( f ); + + COM_Compress(buf); + + ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] ); +} + +/* +=============== +UI_LoadBots +=============== +*/ +void UI_LoadBots( void ) { + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + ui_numBots = 0; + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); + if( *botsFile.string ) { + UI_LoadBotsFromFile(botsFile.string); + } + else { + UI_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); + UI_LoadBotsFromFile(filename); + } +// trap_Print( va( "%i bots parsed\n", ui_numBots ) ); +} + + +/* +=============== +UI_GetBotInfoByNumber +=============== +*/ +char *UI_GetBotInfoByNumber( int num ) { + if( num < 0 || num >= ui_numBots ) { + trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return ui_botInfos[num]; +} + + +/* +=============== +UI_GetBotInfoByName +=============== +*/ +char *UI_GetBotInfoByName( const char *name ) { + int n; + char *value; + + for ( n = 0; n < ui_numBots ; n++ ) { + value = Info_ValueForKey( ui_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return ui_botInfos[n]; + } + } + + return NULL; +} + +int UI_GetNumBots() { + return ui_numBots; +} + + +char *UI_GetBotNameByNumber( int num ) { + char *info = UI_GetBotInfoByNumber(num); + if (info) { + return Info_ValueForKey( info, "name" ); + } + return "Kyle"; +} diff --git a/codemp/ui/ui_local.h b/codemp/ui/ui_local.h new file mode 100644 index 0000000..f72aa20 --- /dev/null +++ b/codemp/ui/ui_local.h @@ -0,0 +1,1166 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#ifndef __UI_LOCAL_H__ +#define __UI_LOCAL_H__ + +#include "../game/q_shared.h" +#include "../cgame/tr_types.h" +#include "ui_public.h" +#include "keycodes.h" +#include "../game/bg_public.h" +#include "ui_shared.h" + +// global display context + +extern vmCvar_t ui_ffa_fraglimit; +extern vmCvar_t ui_ffa_timelimit; + +extern vmCvar_t ui_tourney_fraglimit; +extern vmCvar_t ui_tourney_timelimit; + +extern vmCvar_t ui_selectedModelIndex; + +extern vmCvar_t ui_team_fraglimit; +extern vmCvar_t ui_team_timelimit; +extern vmCvar_t ui_team_friendly; + +extern vmCvar_t ui_ctf_capturelimit; +extern vmCvar_t ui_ctf_timelimit; +extern vmCvar_t ui_ctf_friendly; + +extern vmCvar_t ui_arenasFile; +extern vmCvar_t ui_botsFile; + +extern vmCvar_t ui_browserMaster; +extern vmCvar_t ui_browserGameType; +extern vmCvar_t ui_browserSortKey; +extern vmCvar_t ui_browserShowFull; +extern vmCvar_t ui_browserShowEmpty; + +extern vmCvar_t ui_drawCrosshair; +extern vmCvar_t ui_drawCrosshairNames; +extern vmCvar_t ui_marks; + +extern vmCvar_t ui_server1; +extern vmCvar_t ui_server2; +extern vmCvar_t ui_server3; +extern vmCvar_t ui_server4; +extern vmCvar_t ui_server5; +extern vmCvar_t ui_server6; +extern vmCvar_t ui_server7; +extern vmCvar_t ui_server8; +extern vmCvar_t ui_server9; +extern vmCvar_t ui_server10; +extern vmCvar_t ui_server11; +extern vmCvar_t ui_server12; +extern vmCvar_t ui_server13; +extern vmCvar_t ui_server14; +extern vmCvar_t ui_server15; +extern vmCvar_t ui_server16; + +extern vmCvar_t ui_captureLimit; +extern vmCvar_t ui_fragLimit; +extern vmCvar_t ui_gameType; +extern vmCvar_t ui_netGameType; +extern vmCvar_t ui_actualNetGameType; +extern vmCvar_t ui_joinGameType; +#ifdef _XBOX +extern vmCvar_t ui_optiGameType; +extern vmCvar_t ui_optiCurrentMap; +extern vmCvar_t ui_optiMinPlayers; +extern vmCvar_t ui_optiMaxPlayers; +extern vmCvar_t ui_optiFriendlyFire; +extern vmCvar_t ui_optiJediMastery; +extern vmCvar_t ui_optiSaberOnly; +#endif +extern vmCvar_t ui_netSource; +extern vmCvar_t ui_serverFilterType; +extern vmCvar_t ui_dedicated; +extern vmCvar_t ui_opponentName; +extern vmCvar_t ui_menuFiles; +extern vmCvar_t ui_currentMap; +extern vmCvar_t ui_currentNetMap; +extern vmCvar_t ui_mapIndex; +extern vmCvar_t ui_currentOpponent; +extern vmCvar_t ui_selectedPlayer; +extern vmCvar_t ui_selectedPlayerName; +extern vmCvar_t ui_lastServerRefresh_0; +extern vmCvar_t ui_lastServerRefresh_1; +extern vmCvar_t ui_lastServerRefresh_2; +extern vmCvar_t ui_lastServerRefresh_3; +extern vmCvar_t ui_singlePlayerActive; +extern vmCvar_t ui_scoreAccuracy; +extern vmCvar_t ui_scoreImpressives; +extern vmCvar_t ui_scoreExcellents; +extern vmCvar_t ui_scoreDefends; +extern vmCvar_t ui_scoreAssists; +extern vmCvar_t ui_scoreGauntlets; +extern vmCvar_t ui_scoreScore; +extern vmCvar_t ui_scorePerfect; +extern vmCvar_t ui_scoreTeam; +extern vmCvar_t ui_scoreBase; +extern vmCvar_t ui_scoreTimeBonus; +extern vmCvar_t ui_scoreSkillBonus; +extern vmCvar_t ui_scoreShutoutBonus; +extern vmCvar_t ui_scoreTime; +extern vmCvar_t ui_serverStatusTimeOut; + +extern vmCvar_t ui_bypassMainMenuLoad; + +// +// ui_qmenu.c +// + +#define RCOLUMN_OFFSET ( BIGCHAR_WIDTH ) +#define LCOLUMN_OFFSET (-BIGCHAR_WIDTH ) + +#define SLIDER_RANGE 10 +#define MAX_EDIT_LINE 256 + +#define MAX_MENUDEPTH 8 +#define MAX_MENUITEMS 256 + +#define MAX_FORCE_CONFIGS 128 + +#define MTYPE_NULL 0 +#define MTYPE_SLIDER 1 +#define MTYPE_ACTION 2 +#define MTYPE_SPINCONTROL 3 +#define MTYPE_FIELD 4 +#define MTYPE_RADIOBUTTON 5 +#define MTYPE_BITMAP 6 +#define MTYPE_TEXT 7 +#define MTYPE_SCROLLLIST 8 +#define MTYPE_PTEXT 9 +#define MTYPE_BTEXT 10 + +#define QMF_BLINK 0x00000001 +#define QMF_SMALLFONT 0x00000002 +#define QMF_LEFT_JUSTIFY 0x00000004 +#define QMF_CENTER_JUSTIFY 0x00000008 +#define QMF_RIGHT_JUSTIFY 0x00000010 +#define QMF_NUMBERSONLY 0x00000020 // edit field is only numbers +#define QMF_HIGHLIGHT 0x00000040 +#define QMF_HIGHLIGHT_IF_FOCUS 0x00000080 // steady focus +#define QMF_PULSEIFFOCUS 0x00000100 // pulse if focus +#define QMF_HASMOUSEFOCUS 0x00000200 +#define QMF_NOONOFFTEXT 0x00000400 +#define QMF_MOUSEONLY 0x00000800 // only mouse input allowed +#define QMF_HIDDEN 0x00001000 // skips drawing +#define QMF_GRAYED 0x00002000 // grays and disables +#define QMF_INACTIVE 0x00004000 // disables any input +#define QMF_NODEFAULTINIT 0x00008000 // skip default initialization +#define QMF_OWNERDRAW 0x00010000 +#define QMF_PULSE 0x00020000 +#define QMF_LOWERCASE 0x00040000 // edit field is all lower case +#define QMF_UPPERCASE 0x00080000 // edit field is all upper case +#define QMF_SILENT 0x00100000 + +// callback notifications +#define QM_GOTFOCUS 1 +#define QM_LOSTFOCUS 2 +#define QM_ACTIVATED 3 + +typedef struct _tag_menuframework +{ + int cursor; + int cursor_prev; + + int nitems; + void *items[MAX_MENUITEMS]; + + void (*draw) (void); + sfxHandle_t (*key) (int key); + + qboolean wrapAround; + qboolean fullscreen; + qboolean showlogo; +} menuframework_s; + +typedef struct +{ + int type; + const char *name; + int id; + int x, y; + int left; + int top; + int right; + int bottom; + menuframework_s *parent; + int menuPosition; + unsigned flags; + + void (*callback)( void *self, int event ); + void (*statusbar)( void *self ); + void (*ownerdraw)( void *self ); +} menucommon_s; + +typedef struct { + int cursor; + int scroll; + int widthInChars; + char buffer[MAX_EDIT_LINE]; + int maxchars; +} mfield_t; + +typedef struct +{ + menucommon_s generic; + mfield_t field; +} menufield_s; + +typedef struct +{ + menucommon_s generic; + + float minvalue; + float maxvalue; + float curvalue; + + float range; +} menuslider_s; + +typedef struct +{ + menucommon_s generic; + + int oldvalue; + int curvalue; + int numitems; + int top; + + const char **itemnames; + + int width; + int height; + int columns; + int seperation; +} menulist_s; + +typedef struct +{ + menucommon_s generic; +} menuaction_s; + +typedef struct +{ + menucommon_s generic; + int curvalue; +} menuradiobutton_s; + +typedef struct +{ + menucommon_s generic; + char* focuspic; + char* errorpic; + qhandle_t shader; + qhandle_t focusshader; + int width; + int height; + float* focuscolor; +} menubitmap_s; + +typedef struct +{ + menucommon_s generic; + char* string; + int style; + float* color; +} menutext_s; + +extern void Menu_Cache( void ); +extern void Menu_Focus( menucommon_s *m ); +extern void Menu_AddItem( menuframework_s *menu, void *item ); +extern void Menu_AdjustCursor( menuframework_s *menu, int dir ); +extern void Menu_Draw( menuframework_s *menu ); +extern void *Menu_ItemAtCursor( menuframework_s *m ); +extern sfxHandle_t Menu_ActivateItem( menuframework_s *s, menucommon_s* item ); +extern void Menu_SetCursor( menuframework_s *s, int cursor ); +extern void Menu_SetCursorToItem( menuframework_s *m, void* ptr ); +extern sfxHandle_t Menu_DefaultKey( menuframework_s *s, int key ); +extern void Bitmap_Init( menubitmap_s *b ); +extern void Bitmap_Draw( menubitmap_s *b ); +extern void ScrollList_Draw( menulist_s *l ); +extern sfxHandle_t ScrollList_Key( menulist_s *l, int key ); +extern sfxHandle_t menu_in_sound; +extern sfxHandle_t menu_move_sound; +extern sfxHandle_t menu_out_sound; +extern sfxHandle_t menu_buzz_sound; +extern sfxHandle_t menu_null_sound; +extern sfxHandle_t weaponChangeSound; +extern vec4_t menu_text_color; +extern vec4_t menu_grayed_color; +extern vec4_t menu_dark_color; +extern vec4_t menu_highlight_color; +extern vec4_t menu_red_color; +extern vec4_t menu_black_color; +extern vec4_t menu_dim_color; +extern vec4_t color_black; +extern vec4_t color_white; +extern vec4_t color_yellow; +extern vec4_t color_blue; +extern vec4_t color_orange; +extern vec4_t color_red; +extern vec4_t color_dim; +extern vec4_t name_color; +extern vec4_t list_color; +extern vec4_t listbar_color; +extern vec4_t text_color_disabled; +extern vec4_t text_color_normal; +extern vec4_t text_color_highlight; + +extern char *ui_medalNames[]; +extern char *ui_medalPicNames[]; +extern char *ui_medalSounds[]; + +// +// ui_mfield.c +// +extern void MField_Clear( mfield_t *edit ); +extern void MField_KeyDownEvent( mfield_t *edit, int key ); +extern void MField_CharEvent( mfield_t *edit, int ch ); +extern void MField_Draw( mfield_t *edit, int x, int y, int style, vec4_t color ); +extern void MenuField_Init( menufield_s* m ); +extern void MenuField_Draw( menufield_s *f ); +extern sfxHandle_t MenuField_Key( menufield_s* m, int* key ); + +// +// ui_main.c +// +qboolean UI_FeederSelection( float feederID, int index, itemDef_t *item ); +void UI_Report(); +void UI_Load(); +void UI_LoadMenus(const char *menuFile, qboolean reset); +void _UI_SetActiveMenu( uiMenuCommand_t menu ); +int UI_AdjustTimeByGame(int time); +void UI_ShowPostGame(qboolean newHigh); +void UI_ClearScores(); +void UI_LoadArenas(void); +void UI_LoadForceConfig_List( void ); + +// +// ui_menu.c +// +extern void MainMenu_Cache( void ); +extern void UI_MainMenu(void); +extern void UI_RegisterCvars( void ); +extern void UI_UpdateCvars( void ); + +// +// ui_credits.c +// +extern void UI_CreditMenu( void ); + +// +// ui_ingame.c +// +extern void InGame_Cache( void ); +extern void UI_InGameMenu(void); + +// +// ui_confirm.c +// +extern void ConfirmMenu_Cache( void ); +extern void UI_ConfirmMenu( const char *question, void (*draw)( void ), void (*action)( qboolean result ) ); + +// +// ui_setup.c +// +extern void UI_SetupMenu_Cache( void ); +extern void UI_SetupMenu(void); + +// +// ui_team.c +// +extern void UI_TeamMainMenu( void ); +extern void TeamMain_Cache( void ); + +// +// ui_connect.c +// +extern void UI_DrawConnectScreen( qboolean overlay ); + +// +// ui_controls2.c +// +extern void UI_ControlsMenu( void ); +extern void Controls_Cache( void ); + +// +// ui_demo2.c +// +extern void UI_DemosMenu( void ); +extern void Demos_Cache( void ); + +// +// ui_cinematics.c +// +extern void UI_CinematicsMenu( void ); +extern void UI_CinematicsMenu_f( void ); +extern void UI_CinematicsMenu_Cache( void ); + +// +// ui_mods.c +// +extern void UI_ModsMenu( void ); +extern void UI_ModsMenu_Cache( void ); + +// +// ui_cdkey.c +// +extern void UI_CDKeyMenu( void ); +extern void UI_CDKeyMenu_Cache( void ); +extern void UI_CDKeyMenu_f( void ); + +// +// ui_playermodel.c +// +extern void UI_PlayerModelMenu( void ); +extern void PlayerModel_Cache( void ); + +// +// ui_playersettings.c +// +extern void UI_PlayerSettingsMenu( void ); +extern void PlayerSettings_Cache( void ); + +// +// ui_preferences.c +// +extern void UI_PreferencesMenu( void ); +extern void Preferences_Cache( void ); + +// +// ui_specifyleague.c +// +extern void UI_SpecifyLeagueMenu( void ); +extern void SpecifyLeague_Cache( void ); + +// +// ui_specifyserver.c +// +extern void UI_SpecifyServerMenu( void ); +extern void SpecifyServer_Cache( void ); + +// +// ui_servers2.c +// +#define MAX_FAVORITESERVERS 16 + +extern void UI_ArenaServersMenu( void ); +extern void ArenaServers_Cache( void ); + +// +// ui_startserver.c +// +extern void UI_StartServerMenu( qboolean multiplayer ); +extern void StartServer_Cache( void ); +extern void ServerOptions_Cache( void ); +extern void UI_BotSelectMenu( char *bot ); +extern void UI_BotSelectMenu_Cache( void ); + +// +// ui_serverinfo.c +// +extern void UI_ServerInfoMenu( void ); +extern void ServerInfo_Cache( void ); + +// +// ui_video.c +// +extern void UI_GraphicsOptionsMenu( void ); +extern void GraphicsOptions_Cache( void ); +extern void DriverInfo_Cache( void ); + +// +// ui_players.c +// + +//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_TOTALANIMATIONS]; + + qhandle_t weaponModel; + qhandle_t barrelModel; + qhandle_t flashModel; + 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 newModel; + + qboolean barrelSpinning; + float barrelAngle; + int barrelTime; + + int realWeapon; +} 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, char *teamName ); +//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 , const char *headName, const char *teamName); + +// +// ui_atoms.c +// +// this is only used in the old ui, the new ui has it's own version +typedef struct { + int frametime; + int realtime; + int cursorx; + int cursory; + glconfig_t glconfig; + qboolean debug; + qhandle_t whiteShader; + qhandle_t menuBackShader; + qhandle_t menuBackShader2; + qhandle_t menuBackNoLogoShader; + qhandle_t charset; + qhandle_t cursor; + qhandle_t rb_on; + qhandle_t rb_off; + float scale; + float bias; + qboolean demoversion; + qboolean firstdraw; +} uiStatic_t; + + +// new ui stuff +#define UI_NUMFX 7 +#define MAX_HEADS 64 +#define MAX_ALIASES 64 +#define MAX_HEADNAME 32 +#define MAX_TEAMS 64 +#define MAX_GAMETYPES 16 +#define MAX_MAPS 128 +#define MAX_SPMAPS 16 +#define PLAYERS_PER_TEAM 8//5 +#define MAX_PINGREQUESTS 32 +#define MAX_ADDRESSLENGTH 64 +#define MAX_HOSTNAMELENGTH 22 +#define MAX_MAPNAMELENGTH 16 +#define MAX_STATUSLENGTH 64 +#define MAX_LISTBOXWIDTH 59 +#define UI_FONT_THRESHOLD 0.1 +#define MAX_DISPLAY_SERVERS 2048 +#define MAX_SERVERSTATUS_LINES 128 +#define MAX_SERVERSTATUS_TEXT 1024 +#define MAX_FOUNDPLAYER_SERVERS 16 +#define TEAM_MEMBERS 8//5 +#define GAMES_ALL 0 +#define GAMES_FFA 1 +#define GAMES_HOLOCRON 2 +#define GAMES_TEAMPLAY 3 +#define GAMES_TOURNEY 4 +#define GAMES_CTF 5 +#define MAPS_PER_TIER 3 +#define MAX_TIERS 16 +#define MAX_MODS 64 +#define MAX_DEMOS 256 +#define MAX_MOVIES 256 +#define MAX_Q3PLAYERMODELS 256 +#define MAX_PLAYERMODELS 32 + +#define MAX_SCROLLTEXT_SIZE 4096 +#define MAX_SCROLLTEXT_LINES 64 + +typedef struct { + const char *name; + const char *imageName; + qhandle_t headImage; + const char *base; + qboolean active; + int reference; +} characterInfo; + +typedef struct { + const char *name; + const char *ai; + const char *action; +} aliasInfo; + +typedef struct { + const char *teamName; + const char *imageName; + const char *teamMembers[TEAM_MEMBERS]; + qhandle_t teamIcon; + qhandle_t teamIcon_Metal; + qhandle_t teamIcon_Name; + int cinematic; +} teamInfo; + +typedef struct { + const char *gameType; + int gtEnum; +} gameTypeInfo; + +typedef struct { + const char *mapName; + const char *mapLoadName; + const char *imageName; + const char *opponentName; + int teamMembers; + int typeBits; + int cinematic; + int timeToBeat[MAX_GAMETYPES]; + qhandle_t levelShot; + qboolean active; +} mapInfo; + +typedef struct { + const char *tierName; + const char *maps[MAPS_PER_TIER]; + int gameTypes[MAPS_PER_TIER]; + qhandle_t mapHandles[MAPS_PER_TIER]; +} tierInfo; + +typedef struct serverFilter_s { + const char *description; + const char *basedir; +} serverFilter_t; + +typedef struct { + char adrstr[MAX_ADDRESSLENGTH]; + int start; +} pinglist_t; + + +typedef struct serverStatus_s { + pinglist_t pingList[MAX_PINGREQUESTS]; + int numqueriedservers; + int currentping; + int nextpingtime; + int maxservers; + int refreshtime; + int numServers; + int sortKey; + int sortDir; + int lastCount; + qboolean refreshActive; + int currentServer; + int displayServers[MAX_DISPLAY_SERVERS]; + int numDisplayServers; + int numPlayersOnServers; + int nextDisplayRefresh; + int nextSortTime; + qhandle_t currentServerPreview; + int currentServerCinematic; + int motdLen; + int motdWidth; + int motdPaintX; + int motdPaintX2; + int motdOffset; + int motdTime; + char motd[MAX_STRING_CHARS]; +} serverStatus_t; + + +typedef struct { + char adrstr[MAX_ADDRESSLENGTH]; + char name[MAX_ADDRESSLENGTH]; + int startTime; + int serverNum; + qboolean valid; +} pendingServer_t; + +typedef struct { + int num; + pendingServer_t server[MAX_SERVERSTATUSREQUESTS]; +} pendingServerStatus_t; + +typedef struct { + char address[MAX_ADDRESSLENGTH]; + char *lines[MAX_SERVERSTATUS_LINES][4]; + char text[MAX_SERVERSTATUS_TEXT]; + char pings[MAX_CLIENTS * 3]; + int numLines; +} serverStatusInfo_t; + +typedef struct { + const char *modName; + const char *modDescr; +} modInfo_t; + +typedef struct { + char Name[64]; + int SkinHeadCount; + char SkinHeadNames[MAX_PLAYERMODELS][16]; + int SkinTorsoCount; + char SkinTorsoNames[MAX_PLAYERMODELS][16]; + int SkinLegCount; + 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 newHighScoreTime; + int newBestTime; + int showPostGameTime; + qboolean newHighScore; + qboolean demoAvailable; + qboolean soundHighScore; + + int characterCount; + int botIndex; +// characterInfo characterList[MAX_HEADS]; + + int aliasCount; + aliasInfo aliasList[MAX_ALIASES]; + + int teamCount; + teamInfo teamList[MAX_TEAMS]; + + int numGameTypes; + gameTypeInfo gameTypes[MAX_GAMETYPES]; + + int numJoinGameTypes; + gameTypeInfo joinGameTypes[MAX_GAMETYPES]; + + int redBlue; + int playerCount; + int myTeamCount; + int teamIndex; + int playerRefresh; + int playerIndex; + int playerNumber; + qboolean teamLeader; + char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH]; + int teamClientNums[MAX_CLIENTS]; + + int playerIndexes[MAX_CLIENTS]; //so we can vote-kick by index + + int mapCount; + mapInfo mapList[MAX_MAPS]; + + + int tierCount; + tierInfo tierList[MAX_TIERS]; + + int skillIndex; + + modInfo_t modList[MAX_MODS]; + int modCount; + int modIndex; + + const char *demoList[MAX_DEMOS]; + int demoCount; + int demoIndex; + + const char *movieList[MAX_MOVIES]; + int movieCount; + int movieIndex; + int previewMovie; + + char scrolltext[MAX_SCROLLTEXT_SIZE]; + const char *scrolltextLine[MAX_SCROLLTEXT_LINES]; + int scrolltextLineCount; + + serverStatus_t serverStatus; + + // for the showing the status of a server + char serverStatusAddress[MAX_ADDRESSLENGTH]; + serverStatusInfo_t serverStatusInfo; + int nextServerStatusRefresh; + + // to retrieve the status of server to find a player + pendingServerStatus_t pendingServerStatus; + char findPlayerName[MAX_STRING_CHARS]; + char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; + char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH]; + int currentFoundPlayerServer; + int numFoundPlayerServers; + int nextFindPlayerRefresh; + + int currentCrosshair; + int startPostGameTime; + sfxHandle_t newHighScoreSound; + + int q3HeadCount; + char q3HeadNames[MAX_Q3PLAYERMODELS][64]; + qhandle_t q3HeadIcons[MAX_Q3PLAYERMODELS]; + int q3SelectedHead; + + int forceConfigCount; + int forceConfigSelected; + char forceConfigNames[MAX_FORCE_CONFIGS][128]; + qboolean forceConfigSide[MAX_FORCE_CONFIGS]; //true if it's a light side config, false if dark side + int forceConfigDarkIndexBegin; //mark the index number dark configs start at + int forceConfigLightIndexBegin; //mark the index number light configs start at + + int effectsColor; + + qboolean inGameLoad; + + int playerSpeciesCount; + playerSpeciesInfo_t playerSpecies[MAX_PLAYERMODELS]; + int playerSpeciesIndex; + + short movesTitleIndex; + char *movesBaseAnim; + int moveAnimTime; + + int languageCount; + int languageCountIndex; + +} uiInfo_t; + +extern uiInfo_t uiInfo; + + +extern void UI_Init( void ); +extern void UI_Shutdown( void ); +extern void UI_KeyEvent( int key ); +extern void UI_MouseEvent( int dx, int dy ); +extern void UI_Refresh( int realtime ); +extern qboolean UI_ConsoleCommand( int realTime ); +extern float UI_ClampCvar( float min, float max, float value ); +extern void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ); +extern void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ); +extern void UI_FillRect( float x, float y, float width, float height, const float *color ); +extern void UI_DrawRect( float x, float y, float width, float height, const float *color ); +extern void UI_DrawTopBottom(float x, float y, float w, float h); +extern void UI_DrawSides(float x, float y, float w, float h); +extern void UI_UpdateScreen( void ); +extern void UI_SetColor( const float *rgba ); +extern void UI_LerpColor(vec4_t a, vec4_t b, vec4_t c, float t); +extern void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ); +extern float UI_ProportionalSizeScale( int style ); +extern void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); +extern int UI_ProportionalStringWidth( const char* str ); +extern void UI_DrawString( int x, int y, const char* str, int style, vec4_t color ); +extern void UI_DrawChar( int x, int y, int ch, int style, vec4_t color ); +extern qboolean UI_CursorInRect (int x, int y, int width, int height); +extern void UI_DrawTextBox (int x, int y, int width, int lines); +extern qboolean UI_IsFullscreen( void ); +extern void UI_SetActiveMenu( uiMenuCommand_t menu ); +extern void UI_PushMenu ( menuframework_s *menu ); +extern void UI_PopMenu (void); +extern void UI_ForceMenuOff (void); +extern char *UI_Argv( int arg ); +extern char *UI_Cvar_VariableString( const char *var_name ); +extern void UI_Refresh( int time ); +extern void UI_KeyEvent( int key ); +extern void UI_StartDemoLoop( void ); +extern qboolean m_entersound; +void UI_LoadBestScores(const char *map, int game); +extern uiStatic_t uis; + +// +// ui_spLevel.c +// +void UI_SPLevelMenu_Cache( void ); +void UI_SPLevelMenu( void ); +void UI_SPLevelMenu_f( void ); +void UI_SPLevelMenu_ReInit( void ); + +// +// ui_spArena.c +// +void UI_SPArena_Start( const char *arenaInfo ); + +// +// ui_spPostgame.c +// +void UI_SPPostgameMenu_Cache( void ); +void UI_SPPostgameMenu_f( void ); + +// +// ui_spSkill.c +// +void UI_SPSkillMenu( const char *arenaInfo ); +void UI_SPSkillMenu_Cache( void ); + +// +// ui_syscalls.c +// + +#include "../namespace_begin.h" + +void trap_Print( const char *string ); +void trap_Error( const char *string ); +int trap_Milliseconds( void ); +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 ); +float trap_Cvar_VariableValue( const char *var_name ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); +void trap_Cvar_SetValue( const char *var_name, float value ); +void trap_Cvar_Reset( const char *name ); +void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ); +void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ); +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Cmd_ExecuteText( int exec_when, const char *text ); // don't use EXEC_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 ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +qhandle_t trap_R_RegisterModel( const char *name ); +qhandle_t trap_R_RegisterSkin( const char *name ); +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); +void trap_R_ShaderNameFromIndex(char *name, int index); +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); +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_UpdateScreen( void ); +int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ); +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +sfxHandle_t trap_S_RegisterSound( const char *sample ); +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ); +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ); +void trap_Key_SetBinding( int keynum, const char *binding ); +qboolean trap_Key_IsDown( int keynum ); +qboolean trap_Key_GetOverstrikeMode( void ); +void trap_Key_SetOverstrikeMode( qboolean state ); +void trap_Key_ClearStates( void ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +void trap_GetClipboardData( char *buf, int bufsize ); +void trap_GetClientState( uiClientState_t *state ); +void trap_GetGlconfig( glconfig_t *glconfig ); +int trap_GetConfigString( int index, char* buff, int buffsize ); +int trap_LAN_GetServerCount( int source ); +void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ); +void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ); +int trap_LAN_GetServerPing( int source, int n ); +int trap_LAN_GetPingQueueCount( void ); +void trap_LAN_ClearPing( int n ); +void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ); +void trap_LAN_GetPingInfo( int n, char *buf, int buflen ); +void trap_LAN_LoadCachedServers(); +void trap_LAN_SaveCachedServers(); +void trap_LAN_MarkServerVisible(int source, int n, qboolean visible); +int trap_LAN_ServerIsVisible( int source, int n); +qboolean trap_LAN_UpdateVisiblePings( int source ); +int trap_LAN_AddServer(int source, const char *name, const char *addr); +void trap_LAN_RemoveServer(int source, const char *addr); +void trap_LAN_ResetPings(int n); +int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ); +int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ); +int trap_MemoryRemaining( void ); + +#ifdef USE_CD_KEY + +void trap_GetCDKey( char *buf, int buflen ); +void trap_SetCDKey( char *buf ); +qboolean trap_VerifyCDKey( const char *key, const char *chksum); + +#endif // USE_CD_KEY + +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*/ ); +void trap_S_StopBackgroundTrack( void ); +void trap_S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bReturnWithoutStarting); +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); +int trap_RealTime(qtime_t *qtime); +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +#include "../namespace_end.h" + +// +// ui_addbots.c +// +void UI_AddBots_Cache( void ); +void UI_AddBotsMenu( void ); + +// +// ui_removebots.c +// +void UI_RemoveBots_Cache( void ); +void UI_RemoveBotsMenu( void ); + +// +// ui_teamorders.c +// +extern void UI_TeamOrdersMenu( void ); +extern void UI_TeamOrdersMenu_f( void ); +extern void UI_TeamOrdersMenu_Cache( void ); + +// +// ui_loadconfig.c +// +void UI_LoadConfig_Cache( void ); +void UI_LoadConfigMenu( void ); + +// +// ui_saveconfig.c +// +void UI_SaveConfigMenu_Cache( void ); +void UI_SaveConfigMenu( void ); + +// +// ui_display.c +// +void UI_DisplayOptionsMenu_Cache( void ); +void UI_DisplayOptionsMenu( void ); + +// +// ui_sound.c +// +void UI_SoundOptionsMenu_Cache( void ); +void UI_SoundOptionsMenu( void ); + +// +// ui_network.c +// +void UI_NetworkOptionsMenu_Cache( void ); +void UI_NetworkOptionsMenu( void ); + +// +// ui_gameinfo.c +// +typedef enum { + AWARD_ACCURACY, + AWARD_IMPRESSIVE, + AWARD_EXCELLENT, + AWARD_GAUNTLET, + AWARD_FRAGS, + AWARD_PERFECT +} awardType_t; + +const char *UI_GetArenaInfoByNumber( int num ); +const char *UI_GetArenaInfoByMap( const char *map ); +const char *UI_GetSpecialArenaInfo( const char *tag ); +int UI_GetNumArenas( void ); +int UI_GetNumSPArenas( void ); +int UI_GetNumSPTiers( void ); + +char *UI_GetBotInfoByNumber( int num ); +char *UI_GetBotInfoByName( const char *name ); +int UI_GetNumBots( void ); +void UI_LoadBots( void ); +char *UI_GetBotNameByNumber( int num ); + +void UI_GetBestScore( int level, int *score, int *skill ); +void UI_SetBestScore( int level, int score ); +int UI_TierCompleted( int levelWon ); +qboolean UI_ShowTierVideo( int tier ); +qboolean UI_CanShowTierVideo( int tier ); +int UI_GetCurrentGame( void ); +void UI_NewGame( void ); +void UI_LogAwardData( int award, int data ); +int UI_GetAwardLevel( int award ); + +void UI_SPUnlock_f( void ); +void UI_SPUnlockMedals_f( void ); + +void UI_InitGameinfo( void ); + +// +// ui_login.c +// +void Login_Cache( void ); +void UI_LoginMenu( void ); + +// +// ui_signup.c +// +void Signup_Cache( void ); +void UI_SignupMenu( void ); + +// +// ui_rankstatus.c +// +void RankStatus_Cache( void ); +void UI_RankStatusMenu( void ); + + +// new ui + +#define ASSET_BACKGROUND "uiBackground" + +// for tracking sp game info in Team Arena +typedef struct postGameInfo_s { + int score; + int redScore; + int blueScore; + int perfects; + int accuracy; + int impressives; + int excellents; + int defends; + int assists; + int gauntlets; + int captures; + int time; + int timeBonus; + int shutoutBonus; + int skillBonus; + int baseScore; +} postGameInfo_t; + + + +#endif diff --git a/codemp/ui/ui_main.c b/codemp/ui/ui_main.c new file mode 100644 index 0000000..19401d1 --- /dev/null +++ b/codemp/ui/ui_main.c @@ -0,0 +1,11735 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/* +======================================================================= + +USER INTERFACE MAIN + +======================================================================= +*/ + +// use this to get a demo build without an explicit demo build, i.e. to get the demo ui files to build +//#define PRE_RELEASE_TADEMO + +#include "../ghoul2/G2.h" +#include "ui_local.h" +#include "../qcommon/qfiles.h" +#include "../qcommon/game_version.h" +#include "ui_force.h" +#include "../cgame/animtable.h" //we want this to be compiled into the module because we access it in the shared module. +#include "../game/bg_saga.h" + +#include "..\cgame\holocronicons.h" + +extern void UI_SaberAttachToChar( itemDef_t *item ); + +char *forcepowerDesc[NUM_FORCE_POWERS] = +{ +"@MENUS_OF_EFFECT_JEDI_ONLY_NEFFECT", +"@MENUS_DURATION_IMMEDIATE_NAREA", +"@MENUS_DURATION_5_SECONDS_NAREA", +"@MENUS_DURATION_INSTANTANEOUS", +"@MENUS_INSTANTANEOUS_EFFECT_NAREA", +"@MENUS_DURATION_VARIABLE_20", +"@MENUS_DURATION_INSTANTANEOUS_NAREA", +"@MENUS_OF_EFFECT_LIVING_PERSONS", +"@MENUS_DURATION_VARIABLE_10", +"@MENUS_DURATION_VARIABLE_NAREA", +"@MENUS_DURATION_CONTINUOUS_NAREA", +"@MENUS_OF_EFFECT_JEDI_ALLIES_NEFFECT", +"@MENUS_EFFECT_JEDI_ALLIES_NEFFECT", +"@MENUS_VARIABLE_NAREA_OF_EFFECT", +"@MENUS_EFFECT_NAREA_OF_EFFECT", +"@SP_INGAME_FORCE_SABER_OFFENSE_DESC", +"@SP_INGAME_FORCE_SABER_DEFENSE_DESC", +"@SP_INGAME_FORCE_SABER_THROW_DESC" +}; + +#ifdef _XBOX +#include "../xbox/XBLive.h" +#include "../xbox/XBoxCommon.h" +#include "../xbox/XBVoice.h" + + +/* +=============== +UI_XBL_PlayerListScript +Handle all UI script calls for player list +=============== +*/ +void UI_XBL_PlayerListScript(char **args, const char *name) +{ + if ( !String_Parse(args, &name) || !name ) + { + return; + } + // + // General player-list management + // + else if(Q_stricmp(name, "init") == 0) + { + XBL_PL_Init(); + Menu_SetFeederSelection(NULL, FEEDER_XBL_PLAYERS, 0, NULL); + } + else if(Q_stricmp(name, "shutdown") == 0) + { + XBL_PL_Cleanup(); + } + else if (Q_stricmp(name, "popup") == 0) + { + // Copy selected player's info into the cvars + if (!XBL_PL_SetCurrentInfo()) + return; + + // Show the popup + Menus_ActivateByName("playerlist_popup"); + } + // + // User-specific actions + // + else if (Q_stricmp(name, "friendAdd") == 0) + { + XBL_PL_MakeFriend(); + } + else if (Q_stricmp(name, "friendRemove") == 0) + { + XBL_PL_CancelFriend(); + } + else if (Q_stricmp(name, "toggleMute") == 0) + { + XBL_PL_ToggleMute(); + } + else if (Q_stricmp(name, "sendFeedback") == 0) + { + XBL_PL_SendFeedBack(); + } + else if (Q_stricmp(name, "kick") == 0) + { + XBL_PL_KickPlayer(); + } +} + +void UI_JoinSession() +{ + HRESULT result = XBL_F_PerformMenuAction(UI_F_JOINSESSION); + + // if not S_OK then it was a different game, request disk swap + // + if( result != S_OK ) + { + Menus_ActivateByName("ingame_med_bgd"); + trap_Cvar_Set("ui_xboxreboottitle", "Swap disks");//StringTable_Get(XSTR_SWAP_DISCS)); //"Swap Discs" + XONLINE_FRIEND* curFriend = XBL_F_GetChosenFriend(); + char titleString[MAX_TITLENAME_LEN+1]; + if(curFriend) + { + XBL_F_GetTitleString(curFriend->dwTitleID, titleString); + trap_Cvar_Set( "ui_xboxrebootmessage", va("%s %s. %s","Please insert", titleString, "This will let you play that crappy game instead of this awesome one.")); + } + else + trap_Cvar_Set( "ui_xboxrebootmessage", "Put in the right disc. That'll shut down this fine piece of software" ); + Menus_ActivateByName("xbox_swap_disks"); + } +} + +/* +=============== +UI_XBL_HandleFriendsPopUp +Determines which friend pop up to activate based on friend state. +=============== +*/ +void UI_XBL_HandleFriendsPopUp( void ) +{ + // Get the chosen friend + const XONLINE_FRIEND* curFriend = XBL_F_GetChosenFriend(); + + if( !curFriend ) + return; + + // Is the friend's game joinable? + bool joinAvail = (bool)(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_JOINABLE); + + // We can invite if we're playing... + bool inviteAvail = XBL_F_GetState( XONLINE_FRIENDSTATE_FLAG_PLAYING ); + + // Double check that we're REALLY in a game + uiClientState_t cstate; + trap_GetClientState( &cstate ); + if( cstate.connState != CA_ACTIVE ) + inviteAvail = false; + + // If we're in the same game, neither option makes sense + if( memcmp( &curFriend->sessionID, Net_GetXNKID(), sizeof(XNKID) ) == 0 ) + inviteAvail = joinAvail = false; + + // Put the friend's name into the cvar used to display it in all the popups + trap_Cvar_Set( "fl_selectedName", curFriend->szGamertag ); + + // Based on current friendstates activate correct popup + + // Player has received a game invite fom the indicated friend (Accept/Decline/Remove) + if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_RECEIVEDINVITE ) + Menus_ActivateByName("xbf_ReceivedInvite_popup"); + + // We sent an invite, didn't hear back. Player is online and playing (joinable). (Cancel/Join/Remove) + else if( (curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_SENTINVITE) && + !(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_INVITEACCEPTED) && + !(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_INVITEREJECTED) && + (curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_PLAYING) && + joinAvail ) + Menus_ActivateByName("xbf_SentInviteCanJoin_popup"); + + // We sent an invite, didn't hear back. Player is idle, offline, or in our game (not joinable). (Cancel/Remove) + else if( (curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_SENTINVITE) && + !(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_INVITEACCEPTED) && + !(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_INVITEREJECTED) && + !(curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_PLAYING) ) + Menus_ActivateByName("xbf_SentInviteNoJoin_popup"); + + // Player has received a friend request. (Accept/Decline/Block) + else if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_RECEIVEDREQUEST ) + Menus_ActivateByName("xbf_ReceivedRequest_popup"); + + // Player has sent a friend request. (Revoke) + else if( curFriend->dwFriendState & XONLINE_FRIENDSTATE_FLAG_SENTREQUEST ) + Menus_ActivateByName("xbf_SentRequest_popup"); + + // Now, there are four more options, for all combinations of join and invite being available. + // It really doesn't matter if the user is online or offline. + + // Player is joinable, and we have the option to invite (Invite/Join/Remove) + else if( joinAvail && inviteAvail ) + Menus_ActivateByName("xbf_BasicInviteJoin_popup"); + + // Player is joinable, but we can't send an invitation (Join/Remove) + else if( joinAvail && !inviteAvail ) + Menus_ActivateByName("xbf_BasicJoin_popup"); + + // Player can be invited, but we can't join their game (Invite/Remove) + else if( !joinAvail && inviteAvail ) + Menus_ActivateByName("xbf_BasicInvite_popup"); + + // We can't invite, can't join (Remove) + else + Menus_ActivateByName("xbf_Basic_popup"); +} + +/* +=============== +UI_XBL_FriendsListScript +Handle all UI script calls for friends list +=============== +*/ +void UI_XBL_FriendsListScript(char **args, const char *name) +{ + if ( !String_Parse(args, &name) || !name ) + { + return; + } + // + // General friends-list management + // + else if(Q_stricmp(name, "init") == 0) + { + // VVFIXME - Initialize selection or something? - generate isn't instant! + XBL_F_GenerateFriendsList(); + Menu_SetFeederSelection(NULL, FEEDER_XBL_FRIENDS, 0, NULL); + } + else if(Q_stricmp(name, "shutdown") == 0) + { + XBL_F_ReleaseFriendsList(); + } + else if (Q_stricmp(name, "popup") == 0) + { + // Display the correct popup, depending on many factors + UI_XBL_HandleFriendsPopUp(); + } + // + // Respond to friend requests + // + else if (Q_stricmp(name, "accept") == 0) + { + XBL_F_PerformMenuAction(UI_F_FRIENDACCEPTED); + } + else if (Q_stricmp(name, "decline") == 0) + { + XBL_F_PerformMenuAction(UI_F_FRIENDDECLINE); + } + else if (Q_stricmp(name, "block") == 0) + { + XBL_F_PerformMenuAction(UI_F_FRIENDBLOCK); + } + // VVFIXME - Why is this here? It makes no sense. +// else if (Q_stricmp(name, "request") == 0) +// { +// XBL_F_PerformMenuAction(UI_F_FRIENDREQUESTED); +// } + + // + // Remove an existing friend or cancel a pending friend request + // + else if (Q_stricmp(name, "remove") == 0) + { + XBL_F_PerformMenuAction(UI_F_FRIENDREMOVE); + } + else if (Q_stricmp(name, "cancel") == 0) + { + XBL_F_PerformMenuAction(UI_F_FRIENDCANCEL); + } + + // + // Invite someone to play, or cancel a pending invitation + // + else if (Q_stricmp(name, "invite") == 0) + { + XBL_F_PerformMenuAction(UI_F_GAMEREQUESTED); + } + else if(Q_stricmp(name, "uninvite") == 0) + { + XBL_F_PerformMenuAction(UI_F_GAMECANCEL); + } + + // + // Respond to a game invitation + // + else if (Q_stricmp(name, "acceptInvite") == 0) + { +// strcpy( hostName, "friend" ); + + HRESULT result = XBL_F_PerformMenuAction(UI_F_GAMEACCEPTED); + + // if not S_OK then it was a different game, request disk swap + // + if( result != S_OK ) + { + Menus_ActivateByName("ingame_med_bgd"); + trap_Cvar_Set("ui_xboxreboottitle", "Swap Disks");//StringTable_Get(XSTR_SWAP_DISCS)); //"Swap Disks" + //strcpy( str, "Please insert the appropriate Xbox Game Disk. This will close down your Soldier Of Fortune II Double Helix session and reboot your Xbox."); + XONLINE_FRIEND* curFriend = XBL_F_GetChosenFriend(); + char titleString[MAX_TITLENAME_LEN+1]; + if(curFriend) + { + XBL_F_GetTitleString(curFriend->dwTitleID, titleString); + trap_Cvar_Set( "ui_xboxrebootmessage", va("%s %s. %s", "Please insert", titleString, "Close JA session")); + } + else + trap_Cvar_Set( "ui_xboxrebootmessage", "Plz put in the disk. This will close down this game."); //"Please insert the appropriate Xbox Game Disc. This will close down your Soldier Of Fortune II Double Helix session." + + Menus_ActivateByName("xbox_swap_disks"); + } + + } + else if (Q_stricmp(name, "declineInvite") == 0) + { + XBL_F_PerformMenuAction(UI_F_GAMEDECLINE); + } + else if (Q_stricmp(name, "removeInviter") == 0) + { + XBL_F_PerformMenuAction(UI_F_GAMEFRIENDREMOVED); + } + + // + // Join someone else's game uninvited + // + else if (Q_stricmp(name, "join") == 0) + { + XONLINE_FRIEND* curFriend = XBL_F_GetChosenFriend(); + + if( curFriend && XBL_MM_ThisSessionIsLagging( &curFriend->sessionID ) ) + { + XBL_MM_SetJoinType(VIA_FRIEND_JOIN); + trap_Cvar_Set("ui_xboxreboottitle", " "); + trap_Cvar_Set("ui_xboxrebootmessage", "The net suxors. You might not want to do this."); + Menus_ActivateByName("ingame_small_bgd"); + Menus_ActivateByName("xblive_slow_warning"); + } + else + { + Menus_CloseByName("ingame_small_bgd"); + UI_JoinSession(); + } + } + + // Appear online/offline + else if(Q_stricmp(name, "toggleOnline") == 0) + { + XBL_F_PerformMenuAction(UI_F_TOGGLEONLINE); + } +} +#endif // _XBOX + +// 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_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, +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_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, +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_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, +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_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, +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_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, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +NULL, NULL, 0, MDS_NONE, +}; + + +/* +================ +vmMain + +This is the only way control passes into the module. +!!! This MUST BE THE VERY FIRST FUNCTION compiled into the .qvm file !!! +================ +*/ +vmCvar_t ui_debug; +vmCvar_t ui_initialized; +vmCvar_t ui_char_color_red; +vmCvar_t ui_char_color_green; +vmCvar_t ui_char_color_blue; +vmCvar_t ui_PrecacheModels; +vmCvar_t ui_char_anim; + +//JLFCALLOUT +#ifdef _XBOX +vmCvar_t ui_hideAcallout; +vmCvar_t ui_hideBcallout; +vmCvar_t ui_hideXcallout; +#endif +//END JLFCALLOUT + +void _UI_Init( qboolean ); +void _UI_Shutdown( void ); +void _UI_KeyEvent( int key, qboolean down ); +void _UI_MouseEvent( int dx, int dy ); +void _UI_Refresh( int realtime ); +qboolean _UI_IsFullscreen( void ); +void UI_SetSiegeTeams(void); +extern qboolean UI_SaberModelForSaber( const char *saberName, char *saberModel ); +void UI_SiegeSetCvarsForClass(siegeClass_t *scl); +int UI_SiegeClassNum(siegeClass_t *scl); +void UI_UpdateCvarsForClass(const int team,const baseClass,const int index); +void UI_UpdateSiegeStatusIcons(void); +void UI_ClampMaxPlayers(void); +static void UI_CheckServerName( void ); +static qboolean UI_CheckPassword( void ); +static void UI_JoinServer( void ); + +#include "../namespace_begin.h" +// Functions in BG or ui_shared +void Menu_ShowGroup (menuDef_t *menu, char *itemName, qboolean showFlag); +void Menu_ItemDisable(menuDef_t *menu, char *name,int disableFlag); +int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name); +itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name); + +int BG_GetUIPortrait(const int team, const short classIndex, const short cntIndex); +char *BG_GetUIPortraitFile(const int team, const short classIndex, const short cntIndex); + +siegeClass_t *BG_GetClassOnBaseClass(const int team, const short classIndex, const short cntIndex); + +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 UI_GETAPIVERSION: + return UI_API_VERSION; + + case UI_INIT: + _UI_Init(arg0); + return 0; + + case UI_SHUTDOWN: + _UI_Shutdown(); + return 0; + + case UI_KEY_EVENT: + _UI_KeyEvent( arg0, arg1 ); + return 0; + + case UI_MOUSE_EVENT: + _UI_MouseEvent( arg0, arg1 ); + return 0; + + case UI_REFRESH: + _UI_Refresh( arg0 ); + return 0; + + case UI_IS_FULLSCREEN: + return _UI_IsFullscreen(); + + case UI_SET_ACTIVE_MENU: + _UI_SetActiveMenu( arg0 ); + return 0; + + case UI_CONSOLE_COMMAND: + return UI_ConsoleCommand(arg0); + + case UI_DRAW_CONNECT_SCREEN: + UI_DrawConnectScreen( arg0 ); + return 0; + case UI_HASUNIQUECDKEY: // mod authors need to observe this + return qtrue; // bk010117 - change this to qfalse for mods! + case UI_MENU_RESET: + Menu_Reset(); + return 0; + } + + return -1; +} +#include "../namespace_end.h" + +siegeClassDesc_t g_UIClassDescriptions[MAX_SIEGE_CLASSES]; +siegeTeam_t *siegeTeam1 = NULL; +siegeTeam_t *siegeTeam2 = NULL; +int g_UIGloballySelectedSiegeClass = -1; + +//Cut down version of the stuff used in the game code +//This is just the bare essentials of what we need to load animations properly for ui ghoul2 models. +//This function doesn't need to be sync'd with the BG_ version in bg_panimate.c unless some sort of fundamental change +//is made. Just make sure the variables/functions accessed in ui_shared.c exist in both modules. +qboolean UIPAFtextLoaded = qfalse; +animation_t uiHumanoidAnimations[MAX_TOTALANIMATIONS]; //humanoid animations are the only ones that are statically allocated. + +#include "../namespace_begin.h" +bgLoadedAnim_t bgAllAnims[MAX_ANIM_FILES]; +int uiNumAllAnims = 1; //start off at 0, because 0 will always be assigned to humanoid. +#include "../namespace_end.h" + +animation_t *UI_AnimsetAlloc(void) +{ + assert (uiNumAllAnims < MAX_ANIM_FILES); + bgAllAnims[uiNumAllAnims].anims = (animation_t *) BG_Alloc(sizeof(animation_t)*MAX_TOTALANIMATIONS); + + return bgAllAnims[uiNumAllAnims].anims; +} + +/* +====================== +UI_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc + +====================== +*/ +#include "../namespace_begin.h" +static char UIPAFtext[60000]; +int UI_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 = uiNumAllAnims; + + fileHandle_t f; + int animNum; + + if (!isHumanoid) + { + i = 1; + while (i < uiNumAllAnims) + { //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 = uiHumanoidAnimations; + isHumanoid = qtrue; + nextIndex = 0; + } + else + { + animset = UI_AnimsetAlloc(); + + if (!animset) + { + assert(!"Anim set alloc failed!"); + return -1; + } + } + } + } +#ifdef _DEBUG + else + { + assert(animset); + } +#endif + + // load the file + if (!UIPAFtextLoaded || !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( UIPAFtext ) - 1) ) + { + if (len > 0) + { + Com_Error(ERR_DROP, "%s exceeds the allowed ui-side animation buffer!", filename); + } + return -1; + } + + trap_FS_Read( UIPAFtext, len, f ); + UIPAFtext[len] = 0; + trap_FS_FCloseFile( f ); + } + else + { + return 0; //humanoid index + } + + // parse the text + text_p = UIPAFtext; + 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; +// animset[i].initialLerp = 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); +#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); + } + +// animset[animNum].initialLerp = ceil(1000.0f / fabs(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 + + if (isHumanoid) + { + bgAllAnims[0].anims = animset; + strcpy(bgAllAnims[0].filename, filename); + UIPAFtextLoaded = qtrue; + + usedIndex = 0; + } + else + { + bgAllAnims[nextIndex].anims = animset; + strcpy(bgAllAnims[nextIndex].filename, filename); + + usedIndex = nextIndex; + + if (nextIndex) + { //don't bother increasing the number if this ended up as a humanoid load. + uiNumAllAnims++; + } + else + { + UIPAFtextLoaded = qtrue; + usedIndex = 0; + } + } + + return usedIndex; +} + +//menuDef_t *Menus_FindByName(const char *p); +void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow); + +#include "../namespace_end.h" + +void UpdateForceUsed(); + +char holdSPString[MAX_STRING_CHARS]={0}; +char holdSPString2[MAX_STRING_CHARS]={0}; + +uiInfo_t uiInfo; + +static void UI_StartServerRefresh(qboolean full); +static void UI_StopServerRefresh( void ); +static void UI_DoServerRefresh( void ); +static void UI_BuildServerDisplayList(qboolean force); +static void UI_BuildServerStatus(qboolean force); +static void UI_BuildFindPlayerList(qboolean force); +static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ); +static int UI_MapCountByGameType(qboolean singlePlayer); +static int UI_HeadCountByColor( void ); +static void UI_ParseGameInfo(const char *teamFile); +static const char *UI_SelectedMap(int index, int *actual); +static int UI_GetIndexFromSelection(int actual); +static void UI_SiegeClassCnt( const int team ); + + +int ProcessNewUI( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6 ); +int uiSkinColor=TEAM_FREE; +int uiHoldSkinColor=TEAM_FREE; // Stores the skin color so that in non-team games, the player screen remembers the team you chose, in case you're coming back from the force powers screen. + +static const serverFilter_t serverFilters[] = { + {"MENUS_ALL", "" }, + {"MENUS_JEDI_ACADEMY", "" }, +}; +static const int numServerFilters = sizeof(serverFilters) / sizeof(serverFilter_t); + +static const char *skillLevels[] = { + "SKILL1",//"I Can Win", + "SKILL2",//"Bring It On", + "SKILL3",//"Hurt Me Plenty", + "SKILL4",//"Hardcore", + "SKILL5"//"Nightmare" +}; +static const int numSkillLevels = sizeof(skillLevels) / sizeof(const char*); + + + +static const char *teamArenaGameTypes[] = { + "FFA", + "Holocron", + "JediMaster", + "Duel", + "PowerDuel", + "SP", + "Team FFA", + "Siege", + "CTF", + "CTY", + "TeamTournament" +}; +static int const numTeamArenaGameTypes = sizeof(teamArenaGameTypes) / sizeof(const char*); + + + +static char* netnames[] = { + "???", + "UDP", + "IPX", + NULL +}; + +static int gamecodetoui[] = {4,2,3,0,5,1,6}; +static int uitogamecode[] = {4,6,2,3,1,5,7}; + +const char *UI_GetStringEdString(const char *refSection, const char *refName); + +const char *UI_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"; +} + +// returns either string or NULL for OOR... +// +static const char *GetCRDelineatedString( const char *psStripFileRef, const char *psStripStringRef, int iIndex) +{ + static char sTemp[256]; + const char *psList = UI_GetStringEdString(psStripFileRef, psStripStringRef); + char *p; + + while (iIndex--) + { + psList = strchr(psList,'\n'); + if (!psList){ + return NULL; // OOR + } + psList++; + } + + strcpy(sTemp,psList); + p = strchr(sTemp,'\n'); + if (p) { + *p = '\0'; + } + + return sTemp; +} + + +static const char *GetMonthAbbrevString( int iMonth ) +{ + const char *p = GetCRDelineatedString("MP_INGAME","MONTHS", iMonth); + + return p ? p : "Jan"; // sanity +} + + + + +/* +static const char *netSources[] = { + "Local", + "Internet", + "Favorites" +// "Mplayer" +}; +static const int numNetSources = sizeof(netSources) / sizeof(const char*); +*/ +static const int numNetSources = 3; // now hard-entered in StringEd file +static const char *GetNetSourceString(int iSource) +{ + const char *p = GetCRDelineatedString("MP_INGAME","NET_SOURCES", iSource); + + return p ? p : "??"; +} + + + + +void AssetCache() { + int n; + //if (Assets.textFont == NULL) { + //} + //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); + //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); + uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + uiInfo.uiDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); + uiInfo.uiDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); + uiInfo.uiDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_ORANGE );//trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); + uiInfo.uiDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW );//trap_R_RegisterShaderNoMip( ART_FX_GREEN ); + uiInfo.uiDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_GREEN );//trap_R_RegisterShaderNoMip( ART_FX_TEAL ); + uiInfo.uiDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); + uiInfo.uiDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_PURPLE );//trap_R_RegisterShaderNoMip( ART_FX_CYAN ); + uiInfo.uiDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); + uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + uiInfo.uiDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + uiInfo.uiDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + uiInfo.uiDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + uiInfo.uiDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + uiInfo.uiDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + uiInfo.uiDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + uiInfo.uiDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); + + // Icons for various server settings. + uiInfo.uiDC.Assets.needPass = trap_R_RegisterShaderNoMip( "gfx/menus/needpass" ); + uiInfo.uiDC.Assets.noForce = trap_R_RegisterShaderNoMip( "gfx/menus/noforce" ); + uiInfo.uiDC.Assets.forceRestrict = trap_R_RegisterShaderNoMip( "gfx/menus/forcerestrict" ); + uiInfo.uiDC.Assets.saberOnly = trap_R_RegisterShaderNoMip( "gfx/menus/saberonly" ); + uiInfo.uiDC.Assets.trueJedi = trap_R_RegisterShaderNoMip( "gfx/menus/truejedi" ); + + for( n = 0; n < NUM_CROSSHAIRS; n++ ) { + uiInfo.uiDC.Assets.crosshairShader[n] = trap_R_RegisterShaderNoMip( va("gfx/2d/crosshair%c", 'a' + n ) ); + } + + uiInfo.newHighScoreSound = 0;//trap_S_RegisterSound("sound/feedback/voc_newhighscore.wav"); +} + +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 ); +} + +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 ); +} + +#include "../namespace_begin.h" +int MenuFontToHandle(int iMenuFont) +{ + switch (iMenuFont) + { + case 1: return uiInfo.uiDC.Assets.qhSmallFont; + case 2: return uiInfo.uiDC.Assets.qhMediumFont; + case 3: return uiInfo.uiDC.Assets.qhBigFont; + case 4: return uiInfo.uiDC.Assets.qhSmall2Font; + } + + return uiInfo.uiDC.Assets.qhMediumFont; // 0; +} +#include "../namespace_end.h" + +int Text_Width(const char *text, float scale, int iMenuFont) +{ + int iFontIndex = MenuFontToHandle(iMenuFont); + + return trap_R_Font_StrLenPixels(text, iFontIndex, scale); +} + +int Text_Height(const char *text, float scale, int iMenuFont) +{ + int iFontIndex = MenuFontToHandle(iMenuFont); + + return trap_R_Font_HeightPixels(iFontIndex, scale); +} + +void 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); + // + // kludge.. convert JK2 menu styles to SOF2 printstring ctrl codes... + // + switch (style) + { + case ITEM_TEXTSTYLE_NORMAL: iStyleOR = 0;break; // JK2 normal text + case ITEM_TEXTSTYLE_BLINK: iStyleOR = (int)STYLE_BLINK;break; // JK2 fast blinking + case ITEM_TEXTSTYLE_PULSE: iStyleOR = (int)STYLE_BLINK;break; // JK2 slow pulsing + case ITEM_TEXTSTYLE_SHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow + case ITEM_TEXTSTYLE_OUTLINED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow + case ITEM_TEXTSTYLE_OUTLINESHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow + case ITEM_TEXTSTYLE_SHADOWEDMORE: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow + } + + 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 + ); +} + + +void Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style, int iMenuFont) +{ + Text_Paint(x, y, scale, color, text, 0, limit, style, iMenuFont); + + // now print the cursor as well... (excuse the braces, it's for porting C++ to C) + // + { + char sTemp[1024]; + int iCopyCount = limit ? min(strlen(text), limit) : strlen(text); + iCopyCount = min(iCopyCount,cursorPos); + iCopyCount = min(iCopyCount,sizeof(sTemp)); + + // copy text into temp buffer for pixel measure... + // + strncpy(sTemp,text,iCopyCount); + sTemp[iCopyCount] = '\0'; + + { + int iFontIndex = MenuFontToHandle( iMenuFont ); + int iNextXpos = trap_R_Font_StrLenPixels(sTemp, iFontIndex, scale ); + + Text_Paint(x+iNextXpos, y, scale, color, va("%c",cursor), 0, limit, style|ITEM_TEXTSTYLE_BLINK, iMenuFont); + } + } +} + + +// 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 Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit, int iMenuFont) +{ + // 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, NULL); + psText += iAdvanceCount; + + if (uiLetter > 255) + { + *psOut++ = uiLetter>>8; + *psOut++ = uiLetter&0xFF; + } + else + { + *psOut++ = uiLetter&0xFF; + } + } + *psOutLastGood = '\0'; + + *maxX = 0; // feedback + 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 + Text_Paint(x, y, scale, color, text, adjust, limit, ITEM_TEXTSTYLE_NORMAL, iMenuFont); + } +} + + +void UI_ShowPostGame(qboolean newHigh) { + trap_Cvar_Set ("cg_cameraOrbit", "0"); + trap_Cvar_Set("cg_thirdPerson", "0"); + trap_Cvar_Set( "sv_killserver", "1" ); + uiInfo.soundHighScore = newHigh; + _UI_SetActiveMenu(UIMENU_POSTGAME); +} +/* +================= +_UI_Refresh +================= +*/ + +void UI_DrawCenteredPic(qhandle_t image, int w, int h) { + int x, y; + x = (SCREEN_WIDTH - w) / 2; + y = (SCREEN_HEIGHT - h) / 2; + UI_DrawHandlePic(x, y, w, h, image); +} + +int frameCount = 0; +int startTime; + +vmCvar_t ui_rankChange; +static void UI_BuildPlayerList(); +char parsedFPMessage[1024]; + +#include "../namespace_begin.h" +extern int FPMessageTime; +#include "../namespace_end.h" + +void Text_PaintCenter(float x, float y, float scale, vec4_t color, const char *text, float adjust, int iMenuFont); + +const char *UI_GetStringEdString(const char *refSection, const char *refName) +{ + static char text[1024]={0}; + + trap_SP_GetStringTextString(va("%s_%s", refSection, refName), text, sizeof(text)); + return text; +} + +#define UI_FPS_FRAMES 4 +void _UI_Refresh( int realtime ) +{ + static int index; + static int previousTimes[UI_FPS_FRAMES]; + + //if ( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) { + // return; + //} + + trap_G2API_SetTime(realtime, 0); + trap_G2API_SetTime(realtime, 1); + //ghoul2 timer must be explicitly updated during ui rendering. + + 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); + } +#ifndef _XBOX + // draw cursor + UI_SetColor( NULL ); + if (Menu_Count() > 0) { + UI_DrawHandlePic( uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory, 48, 48, uiInfo.uiDC.Assets.cursor); + } +#endif + +#ifndef NDEBUG + if (uiInfo.uiDC.debug) + { + // cursor coordinates + //FIXME + //UI_DrawString( 0, 0, va("(%d,%d)",uis.cursorx,uis.cursory), UI_LEFT|UI_SMALLFONT, colorRed ); + } +#endif + + if (ui_rankChange.integer) + { + FPMessageTime = realtime + 3000; + + if (!parsedFPMessage[0] /*&& uiMaxRank > ui_rankChange.integer*/) + { + const char *printMessage = UI_GetStringEdString("MP_INGAME", "SET_NEW_RANK"); + + int i = 0; + int p = 0; + int linecount = 0; + + while (printMessage[i] && p < 1024) + { + parsedFPMessage[p] = printMessage[i]; + p++; + i++; + linecount++; + + if (linecount > 64 && printMessage[i] == ' ') + { + parsedFPMessage[p] = '\n'; + p++; + linecount = 0; + } + } + parsedFPMessage[p] = '\0'; + } + + //if (uiMaxRank > ui_rankChange.integer) + { + uiMaxRank = ui_rankChange.integer; + uiForceRank = uiMaxRank; + + /* + while (x < NUM_FORCE_POWERS) + { + //For now just go ahead and clear force powers upon rank change + uiForcePowersRank[x] = 0; + x++; + } + uiForcePowersRank[FP_LEVITATION] = 1; + uiForceUsed = 0; + */ + + //Use BG_LegalizedForcePowers and transfer the result into the UI force settings + UI_ReadLegalForce(); + } + + if (ui_freeSaber.integer && uiForcePowersRank[FP_SABER_OFFENSE] < 1) + { + uiForcePowersRank[FP_SABER_OFFENSE] = 1; + } + if (ui_freeSaber.integer && uiForcePowersRank[FP_SABER_DEFENSE] < 1) + { + uiForcePowersRank[FP_SABER_DEFENSE] = 1; + } + trap_Cvar_Set("ui_rankChange", "0"); + + //remember to update the force power count after changing the max rank + UpdateForceUsed(); + } + + if (ui_freeSaber.integer) + { + bgForcePowerCost[FP_SABER_OFFENSE][FORCE_LEVEL_1] = 0; + bgForcePowerCost[FP_SABER_DEFENSE][FORCE_LEVEL_1] = 0; + } + else + { + bgForcePowerCost[FP_SABER_OFFENSE][FORCE_LEVEL_1] = 1; + bgForcePowerCost[FP_SABER_DEFENSE][FORCE_LEVEL_1] = 1; + } + + /* + if (parsedFPMessage[0] && FPMessageTime > realtime) + { + vec4_t txtCol; + int txtStyle = ITEM_TEXTSTYLE_SHADOWED; + + if ((FPMessageTime - realtime) < 2000) + { + txtCol[0] = colorWhite[0]; + txtCol[1] = colorWhite[1]; + txtCol[2] = colorWhite[2]; + txtCol[3] = (((float)FPMessageTime - (float)realtime)/2000); + + txtStyle = 0; + } + else + { + txtCol[0] = colorWhite[0]; + txtCol[1] = colorWhite[1]; + txtCol[2] = colorWhite[2]; + txtCol[3] = colorWhite[3]; + } + + Text_Paint(10, 0, 1, txtCol, parsedFPMessage, 0, 1024, txtStyle, FONT_MEDIUM); + } + */ + //For now, don't bother. +} + +/* +================= +_UI_Shutdown +================= +*/ +#include "../namespace_begin.h" +void UI_CleanupGhoul2(void); +#include "../namespace_end.h" + +void _UI_Shutdown( void ) { + trap_LAN_SaveCachedServers(); + UI_CleanupGhoul2(); +} + +char *defaultMenu = NULL; + +char *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 defaultMenu; + } + 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 defaultMenu; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + //COM_Compress(buf); + return buf; + +} + +qboolean 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 ) { + + memset(&token, 0, sizeof(pc_token_t)); + + 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; + } + //trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.textFont); + uiInfo.uiDC.Assets.qhMediumFont = trap_R_RegisterFont(token.string); + uiInfo.uiDC.Assets.fontRegistered = qtrue; + continue; + } + + if (Q_stricmp(token.string, "smallFont") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle,&pointSize)) { + return qfalse; + } + //trap_R_RegisterFont(token, pointSize, &uiInfo.uiDC.Assets.smallFont); + uiInfo.uiDC.Assets.qhSmallFont = trap_R_RegisterFont(token.string); + continue; + } + + if (Q_stricmp(token.string, "small2Font") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle,&pointSize)) { + return qfalse; + } + //trap_R_RegisterFont(token, pointSize, &uiInfo.uiDC.Assets.smallFont); + uiInfo.uiDC.Assets.qhSmall2Font = trap_R_RegisterFont(token.string); + continue; + } + + if (Q_stricmp(token.string, "bigFont") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle,&pointSize)) { + return qfalse; + } + //trap_R_RegisterFont(token, pointSize, &uiInfo.uiDC.Assets.bigFont); + uiInfo.uiDC.Assets.qhBigFont = trap_R_RegisterFont(token.string); + continue; + } + + if (Q_stricmp(token.string, "cursor") == 0) + { + if (!PC_String_Parse(handle, &uiInfo.uiDC.Assets.cursorStr)) + { + Com_Printf(S_COLOR_YELLOW,"Bad 1st parameter for keyword 'cursor'"); + return qfalse; + } + uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr); + continue; + } + + // gradientbar + if (Q_stricmp(token.string, "gradientbar") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(token.string); + continue; + } + + // enterMenuSound + if (Q_stricmp(token.string, "menuEnterSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( token.string ); + continue; + } + + // exitMenuSound + if (Q_stricmp(token.string, "menuExitSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( token.string ); + continue; + } + + // itemFocusSound + if (Q_stricmp(token.string, "itemFocusSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( token.string ); + continue; + } + + // menuBuzzSound + if (Q_stricmp(token.string, "menuBuzzSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( token.string ); + continue; + } + + if (Q_stricmp(token.string, "fadeClamp") == 0) { + if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeClamp)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeCycle") == 0) { + if (!PC_Int_Parse(handle, &uiInfo.uiDC.Assets.fadeCycle)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeAmount") == 0) { + if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeAmount)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowX") == 0) { + if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowX)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowY") == 0) { + if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowY)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowColor") == 0) { + if (!PC_Color_Parse(handle, &uiInfo.uiDC.Assets.shadowColor)) { + return qfalse; + } + uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3]; + continue; + } + + if (Q_stricmp(token.string, "moveRollSound") == 0) + { + if (trap_PC_ReadToken(handle,&token)) + { + uiInfo.uiDC.Assets.moveRollSound = trap_S_RegisterSound( token.string ); + } + continue; + } + + if (Q_stricmp(token.string, "moveJumpSound") == 0) + { + if (trap_PC_ReadToken(handle,&token)) + { + uiInfo.uiDC.Assets.moveJumpSound = trap_S_RegisterSound( token.string ); + } + + continue; + } + if (Q_stricmp(token.string, "datapadmoveSaberSound1") == 0) + { + if (trap_PC_ReadToken(handle,&token)) + { + uiInfo.uiDC.Assets.datapadmoveSaberSound1 = trap_S_RegisterSound( token.string ); + } + + continue; + } + + if (Q_stricmp(token.string, "datapadmoveSaberSound2") == 0) + { + if (trap_PC_ReadToken(handle,&token)) + { + uiInfo.uiDC.Assets.datapadmoveSaberSound2 = trap_S_RegisterSound( token.string ); + } + + continue; + } + + if (Q_stricmp(token.string, "datapadmoveSaberSound3") == 0) + { + if (trap_PC_ReadToken(handle,&token)) + { + uiInfo.uiDC.Assets.datapadmoveSaberSound3 = trap_S_RegisterSound( token.string ); + } + + continue; + } + + if (Q_stricmp(token.string, "datapadmoveSaberSound4") == 0) + { + if (trap_PC_ReadToken(handle,&token)) + { + uiInfo.uiDC.Assets.datapadmoveSaberSound4 = trap_S_RegisterSound( token.string ); + } + + continue; + } + + if (Q_stricmp(token.string, "datapadmoveSaberSound5") == 0) + { + if (trap_PC_ReadToken(handle,&token)) + { + uiInfo.uiDC.Assets.datapadmoveSaberSound5 = trap_S_RegisterSound( token.string ); + } + + continue; + } + + if (Q_stricmp(token.string, "datapadmoveSaberSound6") == 0) + { + if (trap_PC_ReadToken(handle,&token)) + { + uiInfo.uiDC.Assets.datapadmoveSaberSound6 = trap_S_RegisterSound( token.string ); + } + + continue; + } + + + // precaching various sound files used in the menus + if (Q_stricmp(token.string, "precacheSound") == 0) + { + const char *tempStr; + if (PC_Script_Parse(handle, &tempStr)) + { + char *soundFile; + do + { + soundFile = COM_ParseExt(&tempStr, qfalse); + if (soundFile[0] != 0 && soundFile[0] != ';') { + trap_S_RegisterSound( soundFile); + } + } while (soundFile[0]); + } + continue; + } + } + return qfalse; +} + + +void UI_Report() { + String_Report(); + //Font_Report(); + +} + +void UI_ParseMenu(const char *menuFile) { + int handle; + pc_token_t token; + + //Com_Printf("Parsing menu file: %s\n", menuFile); + + handle = trap_PC_LoadSource(menuFile); + if (!handle) { + return; + } + + while ( 1 ) { + memset(&token, 0, sizeof(pc_token_t)); + 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 (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 Load_Menu(int handle) { + pc_token_t token; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (token.string[0] != '{') { + return qfalse; + } + + while ( 1 ) { + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + + if ( token.string[0] == 0 ) { + return qfalse; + } + + if ( token.string[0] == '}' ) { + return qtrue; + } + + UI_ParseMenu(token.string); + } + return qfalse; +} + +void UI_LoadMenus(const char *menuFile, qboolean reset) { + pc_token_t token; + int handle; + int start; + + start = trap_Milliseconds(); + + trap_PC_LoadGlobalDefines ( "ui/jamp/menudef.h" ); + + handle = trap_PC_LoadSource( menuFile ); + if (!handle) { + Com_Printf( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ); + handle = trap_PC_LoadSource( "ui/jampmenus.txt" ); + if (!handle) { + trap_Error( va( S_COLOR_RED "default menu file not found: ui/menus.txt, unable to continue!\n", menuFile ) ); + } + } + + if (reset) { + Menu_Reset(); + } + + while ( 1 ) { + if (!trap_PC_ReadToken(handle, &token)) + break; + if( token.string[0] == 0 || token.string[0] == '}') { + break; + } + + if ( token.string[0] == '}' ) { + break; + } + + if (Q_stricmp(token.string, "loadmenu") == 0) { + if (Load_Menu(handle)) { + continue; + } else { + break; + } + } + } + +// Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); + + trap_PC_FreeSource( handle ); + + trap_PC_RemoveAllGlobalDefines ( ); +} + +void UI_Load() { + 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/jampingame.txt"; + } + else + { + menuSet= UI_Cvar_VariableString("ui_menuFilesMP"); + } + if (menuSet == NULL || menuSet[0] == '\0') { + menuSet = "ui/jampmenus.txt"; + } + + String_Init(); + +#ifdef PRE_RELEASE_TADEMO + UI_ParseGameInfo("demogameinfo.txt"); +#else + UI_ParseGameInfo("ui/jamp/gameinfo.txt"); +#endif + UI_LoadArenas(); + UI_LoadBots(); + + UI_LoadMenus(menuSet, qtrue); + Menus_CloseAll(); + Menus_ActivateByName(lastName); + +} + +static const char *handicapValues[] = {"None","95","90","85","80","75","70","65","60","55","50","45","40","35","30","25","20","15","10","5",NULL}; + +static void UI_DrawHandicap(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + int i, h; + + h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); + i = 20 - h / 5; + + Text_Paint(rect->x, rect->y, scale, color, handicapValues[i], 0, 0, textStyle, iMenuFont); +} + +static void UI_DrawClanName(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_teamName"), 0, 0, textStyle, iMenuFont); +} + + +static void UI_SetCapFragLimits(qboolean uiVars) { + int cap = 5; + int frag = 10; + + if (uiVars) { + trap_Cvar_Set("ui_captureLimit", va("%d", cap)); + trap_Cvar_Set("ui_fragLimit", va("%d", frag)); + } else { + trap_Cvar_Set("capturelimit", va("%d", cap)); + trap_Cvar_Set("fraglimit", va("%d", frag)); + } +} + +static const char* UI_GetGameTypeName(int gtEnum) +{ + switch ( gtEnum ) + { + case GT_FFA: + return UI_GetStringEdString("MENUS", "FREE_FOR_ALL");//"Free For All"; + case GT_HOLOCRON: + return UI_GetStringEdString("MENUS", "HOLOCRON_FFA");//"Holocron FFA"; + case GT_JEDIMASTER: + return UI_GetStringEdString("MENUS", "SAGA");//"Jedi Master";?? + case GT_SINGLE_PLAYER: + return UI_GetStringEdString("MENUS", "SAGA");//"Team FFA"; + case GT_DUEL: + return UI_GetStringEdString("MENUS", "DUEL");//"Team FFA"; + case GT_POWERDUEL: + return UI_GetStringEdString("MENUS", "POWERDUEL");//"Team FFA"; + case GT_TEAM: + return UI_GetStringEdString("MENUS", "TEAM_FFA");//"Team FFA"; + case GT_SIEGE: + return UI_GetStringEdString("MENUS", "SIEGE");//"Siege"; + case GT_CTF: + return UI_GetStringEdString("MENUS", "CAPTURE_THE_FLAG");//"Capture the Flag"; + case GT_CTY: + return UI_GetStringEdString("MENUS", "CAPTURE_THE_YSALIMARI");//"Capture the Ysalamiri"; + } + return UI_GetStringEdString("MENUS", "SAGA");//"Team FFA"; +} + + + +// ui_gameType assumes gametype 0 is -1 ALL and will not show +static void UI_DrawGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) +{ + Text_Paint(rect->x, rect->y, scale, color, UI_GetGameTypeName(uiInfo.gameTypes[ui_gameType.integer].gtEnum), 0, 0, textStyle, iMenuFont); +} + +static void UI_DrawNetGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) +{ + if (ui_netGameType.integer < 0 || ui_netGameType.integer >= uiInfo.numGameTypes) + { + trap_Cvar_Set("ui_netGameType", "0"); + trap_Cvar_Set("ui_actualNetGameType", "0"); + } + Text_Paint(rect->x, rect->y, scale, color, UI_GetGameTypeName(uiInfo.gameTypes[ui_netGameType.integer].gtEnum) , 0, 0, textStyle, iMenuFont); +} + +static void UI_DrawAutoSwitch(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + int switchVal = trap_Cvar_VariableValue("cg_autoswitch"); + const char *switchString = "AUTOSWITCH1"; + const char *stripString = NULL; + + switch(switchVal) + { + case 2: + switchString = "AUTOSWITCH2"; + break; + case 3: + switchString = "AUTOSWITCH3"; + break; + case 0: + switchString = "AUTOSWITCH0"; + break; + default: + break; + } + + stripString = UI_GetStringEdString("MP_INGAME", (char *)switchString); + + if (stripString) + { + Text_Paint(rect->x, rect->y, scale, color, stripString, 0, 0, textStyle, iMenuFont); + } +} + +static void UI_DrawJoinGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) +{ + if (ui_joinGameType.integer < 0 || ui_joinGameType.integer > uiInfo.numJoinGameTypes) + { + trap_Cvar_Set("ui_joinGameType", "0"); + } + + Text_Paint(rect->x, rect->y, scale, color, UI_GetGameTypeName(uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum) , 0, 0, textStyle, iMenuFont); +} + + + +static int UI_TeamIndexFromName(const char *name) { + int i; + + if (name && *name) { + for (i = 0; i < uiInfo.teamCount; i++) { + if (Q_stricmp(name, uiInfo.teamList[i].teamName) == 0) { + return i; + } + } + } + + return 0; + +} + +static void UI_DrawClanLogo(rectDef_t *rect, float scale, vec4_t color) { + int i; + i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + if (i >= 0 && i < uiInfo.teamCount) { + trap_R_SetColor( color ); + + if (uiInfo.teamList[i].teamIcon == -1) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); + } + + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon); + trap_R_SetColor(NULL); + } +} + +static void UI_DrawClanCinematic(rectDef_t *rect, float scale, vec4_t color) { + int i; + i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + if (i >= 0 && i < uiInfo.teamCount) { + + if (uiInfo.teamList[i].cinematic >= -2) { + if (uiInfo.teamList[i].cinematic == -1) { + uiInfo.teamList[i].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.teamList[i].imageName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); + } + if (uiInfo.teamList[i].cinematic >= 0) { + trap_CIN_RunCinematic(uiInfo.teamList[i].cinematic); + trap_CIN_SetExtents(uiInfo.teamList[i].cinematic, rect->x, rect->y, rect->w, rect->h); + trap_CIN_DrawCinematic(uiInfo.teamList[i].cinematic); + } else { + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal); + trap_R_SetColor(NULL); + uiInfo.teamList[i].cinematic = -2; + } + } else { + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon); + trap_R_SetColor(NULL); + } + } + +} + +static void UI_DrawPreviewCinematic(rectDef_t *rect, float scale, vec4_t color) { + if (uiInfo.previewMovie > -2) { + uiInfo.previewMovie = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.movieList[uiInfo.movieIndex]), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); + if (uiInfo.previewMovie >= 0) { + trap_CIN_RunCinematic(uiInfo.previewMovie); + trap_CIN_SetExtents(uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h); + trap_CIN_DrawCinematic(uiInfo.previewMovie); + } else { + uiInfo.previewMovie = -2; + } + } + +} + +static void UI_DrawSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + int i; + i = trap_Cvar_VariableValue( "g_spSkill" ); + if (i < 1 || i > numSkillLevels) { + i = 1; + } + Text_Paint(rect->x, rect->y, scale, color, (char *)UI_GetStringEdString("MP_INGAME", (char *)skillLevels[i-1]),0, 0, textStyle, iMenuFont); +} + + +static void UI_DrawGenericNum(rectDef_t *rect, float scale, vec4_t color, int textStyle, int val, int min, int max, int type,int iMenuFont) +{ + int i; + char s[256]; + + i = val; + if (i < min || i > max) + { + i = min; + } + + Com_sprintf(s, sizeof(s), "%i\0", val); + Text_Paint(rect->x, rect->y, scale, color, s,0, 0, textStyle, iMenuFont); +} + +static void UI_DrawForceMastery(rectDef_t *rect, float scale, vec4_t color, int textStyle, int val, int min, int max, int iMenuFont) +{ + int i; + char *s; + + i = val; + if (i < min || i > max) + { + i = min; + } + + s = (char *)UI_GetStringEdString("MP_INGAME", forceMasteryLevels[val]); + Text_Paint(rect->x, rect->y, scale, color, s, 0, 0, textStyle, iMenuFont); +} + + +static void UI_DrawSkinColor(rectDef_t *rect, float scale, vec4_t color, int textStyle, int val, int min, int max, int iMenuFont) +{ + int i; + char s[256]; + + i = val; + if (i < min || i > max) + { + i = min; + } + + switch(val) + { + case TEAM_RED: + trap_SP_GetStringTextString("MENUS_TEAM_RED", s, sizeof(s)); +// Com_sprintf(s, sizeof(s), "Red\0"); + break; + case TEAM_BLUE: + trap_SP_GetStringTextString("MENUS_TEAM_BLUE", s, sizeof(s)); +// Com_sprintf(s, sizeof(s), "Blue\0"); + break; + default: + trap_SP_GetStringTextString("MENUS_DEFAULT", s, sizeof(s)); +// Com_sprintf(s, sizeof(s), "Default\0"); + break; + } + + Text_Paint(rect->x, rect->y, scale, color, s, 0, 0, textStyle, iMenuFont); +} + +static void UI_DrawForceSide(rectDef_t *rect, float scale, vec4_t color, int textStyle, int val, int min, int max, int iMenuFont) +{ + int i; + char s[256]; + menuDef_t *menu; + + char info[MAX_INFO_VALUE]; + + i = val; + if (i < min || i > max) + { + i = min; + } + + info[0] = '\0'; + trap_GetConfigString(CS_SERVERINFO, info, sizeof(info)); + + if (atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) )) + { + switch((int)(trap_Cvar_VariableValue("ui_myteam"))) + { + case TEAM_RED: + uiForceSide = FORCE_DARKSIDE; + color[0] = 0.2; + color[1] = 0.2; + color[2] = 0.2; + break; + case TEAM_BLUE: + uiForceSide = FORCE_LIGHTSIDE; + color[0] = 0.2; + color[1] = 0.2; + color[2] = 0.2; + break; + default: + break; + } + } + + if (val == FORCE_LIGHTSIDE) + { + trap_SP_GetStringTextString("MENUS_FORCEDESC_LIGHT",s, sizeof(s)); + menu = Menus_FindByName("forcealloc"); + if (menu) + { + Menu_ShowItemByName(menu, "lightpowers", qtrue); + Menu_ShowItemByName(menu, "darkpowers", qfalse); + Menu_ShowItemByName(menu, "darkpowers_team", qfalse); + + Menu_ShowItemByName(menu, "lightpowers_team", qtrue);//(ui_gameType.integer >= GT_TEAM)); + + } + menu = Menus_FindByName("ingame_playerforce"); + if (menu) + { + Menu_ShowItemByName(menu, "lightpowers", qtrue); + Menu_ShowItemByName(menu, "darkpowers", qfalse); + Menu_ShowItemByName(menu, "darkpowers_team", qfalse); + + Menu_ShowItemByName(menu, "lightpowers_team", qtrue);//(ui_gameType.integer >= GT_TEAM)); + } + } + else + { + trap_SP_GetStringTextString("MENUS_FORCEDESC_DARK",s, sizeof(s)); + menu = Menus_FindByName("forcealloc"); + if (menu) + { + Menu_ShowItemByName(menu, "lightpowers", qfalse); + Menu_ShowItemByName(menu, "lightpowers_team", qfalse); + Menu_ShowItemByName(menu, "darkpowers", qtrue); + + Menu_ShowItemByName(menu, "darkpowers_team", qtrue);//(ui_gameType.integer >= GT_TEAM)); + } + menu = Menus_FindByName("ingame_playerforce"); + if (menu) + { + Menu_ShowItemByName(menu, "lightpowers", qfalse); + Menu_ShowItemByName(menu, "lightpowers_team", qfalse); + Menu_ShowItemByName(menu, "darkpowers", qtrue); + + Menu_ShowItemByName(menu, "darkpowers_team", qtrue);//(ui_gameType.integer >= GT_TEAM)); + } + } + + Text_Paint(rect->x, rect->y, scale, color, s,0, 0, textStyle, iMenuFont); +} + +qboolean UI_HasSetSaberOnly( void ) +{ + char info[MAX_INFO_STRING]; + int i = 0; + int wDisable = 0; + int gametype = 0; + + gametype = atoi(Info_ValueForKey(info, "g_gametype")); + + if ( gametype == GT_JEDIMASTER ) + { //set to 0 + return qfalse; + } + + trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); + + if (gametype == GT_DUEL || gametype == GT_POWERDUEL) + { + wDisable = atoi(Info_ValueForKey(info, "g_duelWeaponDisable")); + } + else + { + wDisable = atoi(Info_ValueForKey(info, "g_weaponDisable")); + } + + while (i < WP_NUM_WEAPONS) + { + if (!(wDisable & (1 << i)) && + i != WP_SABER && i != WP_NONE) + { + return qfalse; + } + + i++; + } + + return qtrue; +} + +static qboolean UI_AllForceDisabled(int force) +{ + int i; + + if (force) + { + for (i=0;i max) + { + i = min; + } + + info[0] = '\0'; + trap_GetConfigString(CS_SERVERINFO, info, sizeof(info)); + + if ( !UI_TrueJediEnabled() ) + {//true jedi mode is not on, do not draw this button type + return; + } + + if ( val == FORCE_NONJEDI ) + { + trap_SP_GetStringTextString("MENUS_NO",s, sizeof(s)); + } + else + { + trap_SP_GetStringTextString("MENUS_YES",s, sizeof(s)); + } + + Text_Paint(rect->x, rect->y, scale, color, s,0, 0, textStyle, iMenuFont); +} + +static void UI_DrawTeamName(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int textStyle, int iMenuFont) { + int i; + i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam")); + if (i >= 0 && i < uiInfo.teamCount) { + Text_Paint(rect->x, rect->y, scale, color, va("%s: %s", (blue) ? "Blue" : "Red", uiInfo.teamList[i].teamName),0, 0, textStyle, iMenuFont); + } +} + +static void UI_DrawTeamMember(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int num, int textStyle, int iMenuFont) +{ + // 0 - None + // 1 - Human + // 2..NumCharacters - Bot + int value = trap_Cvar_VariableValue(va(blue ? "ui_blueteam%i" : "ui_redteam%i", num)); + const char *text; + int maxcl = trap_Cvar_VariableValue( "sv_maxClients" ); + vec4_t finalColor; + int numval = num; + + numval *= 2; + + if (blue) + { + numval -= 1; + } + + finalColor[0] = color[0]; + finalColor[1] = color[1]; + finalColor[2] = color[2]; + finalColor[3] = color[3]; + + if (numval > maxcl) + { + finalColor[0] *= 0.5; + finalColor[1] *= 0.5; + finalColor[2] *= 0.5; + + value = -1; + } + + if (uiInfo.gameTypes[ui_netGameType.integer].gtEnum == GT_SIEGE) + { + if (value > 1 ) + { + value = 1; + } + } + + if (value <= 1) { + if (value == -1) + { + //text = "Closed"; + text = UI_GetStringEdString("MENUS", "CLOSED"); + } + else + { + //text = "Human"; + text = UI_GetStringEdString("MENUS", "HUMAN"); + } + } else { + value -= 2; + if (value >= UI_GetNumBots()) { + value = 1; + } + text = UI_GetBotNameByNumber(value); + } + + Text_Paint(rect->x, rect->y, scale, finalColor, text, 0, 0, textStyle, iMenuFont); +} + +static void UI_DrawEffects(rectDef_t *rect, float scale, vec4_t color) +{ + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiSaberColorShaders[uiInfo.effectsColor]); +} + +static void UI_DrawMapPreview(rectDef_t *rect, float scale, vec4_t color, qboolean net) { + int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; + if (map < 0 || map > uiInfo.mapCount) { + if (net) { + ui_currentNetMap.integer = 0; + trap_Cvar_Set("ui_currentNetMap", "0"); + } else { + ui_currentMap.integer = 0; + trap_Cvar_Set("ui_currentMap", "0"); + } + map = 0; + } + + if (uiInfo.mapList[map].levelShot == -1) { + uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[map].imageName); + } + + if (uiInfo.mapList[map].levelShot > 0) { + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot); + } else { + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("menu/art/unknownmap_mp")); + } +} + + +static void UI_DrawMapTimeToBeat(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + int minutes, seconds, time; + if (ui_currentMap.integer < 0 || ui_currentMap.integer > uiInfo.mapCount) { + ui_currentMap.integer = 0; + trap_Cvar_Set("ui_currentMap", "0"); + } + + time = uiInfo.mapList[ui_currentMap.integer].timeToBeat[uiInfo.gameTypes[ui_gameType.integer].gtEnum]; + + minutes = time / 60; + seconds = time % 60; + + Text_Paint(rect->x, rect->y, scale, color, va("%02i:%02i", minutes, seconds), 0, 0, textStyle, iMenuFont); +} + + + +static void UI_DrawMapCinematic(rectDef_t *rect, float scale, vec4_t color, qboolean net) { + + int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; + if (map < 0 || map > uiInfo.mapCount) { + if (net) { + ui_currentNetMap.integer = 0; + trap_Cvar_Set("ui_currentNetMap", "0"); + } else { + ui_currentMap.integer = 0; + trap_Cvar_Set("ui_currentMap", "0"); + } + map = 0; + } + + if (uiInfo.mapList[map].cinematic >= -1) { + if (uiInfo.mapList[map].cinematic == -1) { + uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[map].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); + } + if (uiInfo.mapList[map].cinematic >= 0) { + trap_CIN_RunCinematic(uiInfo.mapList[map].cinematic); + trap_CIN_SetExtents(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h); + trap_CIN_DrawCinematic(uiInfo.mapList[map].cinematic); + } else { + uiInfo.mapList[map].cinematic = -2; + } + } else { + UI_DrawMapPreview(rect, scale, color, net); + } +} + +static void UI_SetForceDisabled(int force) +{ + int i = 0; + + if (force) + { + while (i < NUM_FORCE_POWERS) + { + if (force & (1 << i)) + { + uiForcePowersDisabled[i] = qtrue; + + if (i != FP_LEVITATION && i != FP_SABER_OFFENSE && i != FP_SABER_DEFENSE) + { + uiForcePowersRank[i] = 0; + } + else + { + if (i == FP_LEVITATION) + { + uiForcePowersRank[i] = 1; + } + else + { + uiForcePowersRank[i] = 3; + } + } + } + else + { + uiForcePowersDisabled[i] = qfalse; + } + i++; + } + } + else + { + i = 0; + + while (i < NUM_FORCE_POWERS) + { + uiForcePowersDisabled[i] = qfalse; + i++; + } + } +} +// The game type on create server has changed - make the HUMAN/BOTS fields active +void UpdateBotButtons(void) +{ + menuDef_t *menu; + + menu = Menu_GetFocused(); + + if (!menu) + { + return; + } + + if (uiInfo.gameTypes[ui_netGameType.integer].gtEnum == GT_SIEGE) + { + Menu_ShowItemByName(menu, "humanbotfield", qfalse); + Menu_ShowItemByName(menu, "humanbotnonfield", qtrue); + } + else + { + Menu_ShowItemByName(menu, "humanbotfield", qtrue); + Menu_ShowItemByName(menu, "humanbotnonfield", qfalse); + } + +} + +void UpdateForceStatus() +{ + menuDef_t *menu; + + // Currently we don't make a distinction between those that wish to play Jedi of lower than maximum skill. +/* if (ui_forcePowerDisable.integer) + { + uiForceRank = 0; + uiForceAvailable = 0; + uiForceUsed = 0; + } + else + { + uiForceRank = uiMaxRank; + uiForceUsed = 0; + uiForceAvailable = forceMasteryPoints[uiForceRank]; + } +*/ + menu = Menus_FindByName("ingame_player"); + if (menu) + { + char info[MAX_INFO_STRING]; + int disabledForce = 0; + qboolean trueJedi = qfalse, allForceDisabled = qfalse; + + trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); + + //already have serverinfo at this point for stuff below. Don't bother trying to use ui_forcePowerDisable. + //if (ui_forcePowerDisable.integer) + //if (atoi(Info_ValueForKey(info, "g_forcePowerDisable"))) + disabledForce = atoi(Info_ValueForKey(info, "g_forcePowerDisable")); + allForceDisabled = UI_AllForceDisabled(disabledForce); + trueJedi = UI_TrueJediEnabled(); + + if ( !trueJedi || allForceDisabled ) + { + Menu_ShowItemByName(menu, "jedinonjedi", qfalse); + } + else + { + Menu_ShowItemByName(menu, "jedinonjedi", qtrue); + } + if ( allForceDisabled == qtrue || (trueJedi && uiJediNonJedi == FORCE_NONJEDI) ) + { // No force stuff + Menu_ShowItemByName(menu, "noforce", qtrue); + Menu_ShowItemByName(menu, "yesforce", qfalse); + // We don't want the saber explanation to say "configure saber attack 1" since we can't. + Menu_ShowItemByName(menu, "sabernoneconfigme", qfalse); + } + else + { + UI_SetForceDisabled(disabledForce); + Menu_ShowItemByName(menu, "noforce", qfalse); + Menu_ShowItemByName(menu, "yesforce", qtrue); + } + + //Moved this to happen after it's done with force power disabling stuff + if (uiForcePowersRank[FP_SABER_OFFENSE] > 0 || ui_freeSaber.integer) + { // Show lightsaber stuff. + Menu_ShowItemByName(menu, "nosaber", qfalse); + Menu_ShowItemByName(menu, "yessaber", qtrue); + } + else + { + Menu_ShowItemByName(menu, "nosaber", qtrue); + Menu_ShowItemByName(menu, "yessaber", qfalse); + } + + // The leftmost button should be "apply" unless you are in spectator, where you can join any team. + if ((int)(trap_Cvar_VariableValue("ui_myteam")) != TEAM_SPECTATOR) + { + Menu_ShowItemByName(menu, "playerapply", qtrue); + Menu_ShowItemByName(menu, "playerforcejoin", qfalse); + Menu_ShowItemByName(menu, "playerforcered", qtrue); + Menu_ShowItemByName(menu, "playerforceblue", qtrue); + Menu_ShowItemByName(menu, "playerforcespectate", qtrue); + } + else + { + // Set or reset buttons based on choices + if (atoi(Info_ValueForKey(info, "g_gametype")) >= GT_TEAM) + { // This is a team-based game. + Menu_ShowItemByName(menu, "playerforcespectate", qtrue); + + // This is disabled, always show both sides from spectator. + if ( 0 && atoi(Info_ValueForKey(info, "g_forceBasedTeams"))) + { // Show red or blue based on what side is chosen. + if (uiForceSide==FORCE_LIGHTSIDE) + { + Menu_ShowItemByName(menu, "playerforcered", qfalse); + Menu_ShowItemByName(menu, "playerforceblue", qtrue); + } + else if (uiForceSide==FORCE_DARKSIDE) + { + Menu_ShowItemByName(menu, "playerforcered", qtrue); + Menu_ShowItemByName(menu, "playerforceblue", qfalse); + } + else + { + Menu_ShowItemByName(menu, "playerforcered", qtrue); + Menu_ShowItemByName(menu, "playerforceblue", qtrue); + } + } + else + { + Menu_ShowItemByName(menu, "playerforcered", qtrue); + Menu_ShowItemByName(menu, "playerforceblue", qtrue); + } + } + else + { + Menu_ShowItemByName(menu, "playerforcered", qfalse); + Menu_ShowItemByName(menu, "playerforceblue", qfalse); + } + + Menu_ShowItemByName(menu, "playerapply", qfalse); + Menu_ShowItemByName(menu, "playerforcejoin", qtrue); + Menu_ShowItemByName(menu, "playerforcespectate", qtrue); + } + } + + + if ( !UI_TrueJediEnabled() ) + {// Take the current team and force a skin color based on it. + char info[MAX_INFO_STRING]; + + switch((int)(trap_Cvar_VariableValue("ui_myteam"))) + { + case TEAM_RED: + uiSkinColor = TEAM_RED; + uiInfo.effectsColor = SABER_RED; + break; + case TEAM_BLUE: + uiSkinColor = TEAM_BLUE; + uiInfo.effectsColor = SABER_BLUE; + break; + default: + trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); + + if (atoi(Info_ValueForKey(info, "g_gametype")) >= GT_TEAM) + { + uiSkinColor = TEAM_FREE; + } + else // A bit of a hack so non-team games will remember which skin set you chose in the player menu + { + uiSkinColor = uiHoldSkinColor; + } + break; + } + } +} + + + +static void UI_DrawNetSource(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) +{ + if (ui_netSource.integer < 0 || ui_netSource.integer > uiInfo.numGameTypes) + { + ui_netSource.integer = 0; + } + + trap_SP_GetStringTextString("MENUS_SOURCE", holdSPString, sizeof(holdSPString) ); + Text_Paint(rect->x, rect->y, scale, color, va("%s %s",holdSPString, + GetNetSourceString(ui_netSource.integer)), 0, 0, textStyle, iMenuFont); +} + +static void UI_DrawNetMapPreview(rectDef_t *rect, float scale, vec4_t color) { + + if (uiInfo.serverStatus.currentServerPreview > 0) { + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview); + } else { + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("menu/art/unknownmap_mp")); + } +} + +static void UI_DrawNetMapCinematic(rectDef_t *rect, float scale, vec4_t color) { + if (ui_currentNetMap.integer < 0 || ui_currentNetMap.integer > uiInfo.mapCount) { + ui_currentNetMap.integer = 0; + trap_Cvar_Set("ui_currentNetMap", "0"); + } + + if (uiInfo.serverStatus.currentServerCinematic >= 0) { + trap_CIN_RunCinematic(uiInfo.serverStatus.currentServerCinematic); + trap_CIN_SetExtents(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h); + trap_CIN_DrawCinematic(uiInfo.serverStatus.currentServerCinematic); + } else { + UI_DrawNetMapPreview(rect, scale, color); + } +} + + + +static void UI_DrawNetFilter(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) +{ + + if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) + { + ui_serverFilterType.integer = 0; + } + + trap_SP_GetStringTextString("MENUS_GAME", holdSPString, sizeof(holdSPString)); + + trap_SP_GetStringTextString(serverFilters[ui_serverFilterType.integer].description, holdSPString2, sizeof(holdSPString2)); + + Text_Paint(rect->x, rect->y, scale, color, va("%s %s",holdSPString, + holdSPString2), 0, 0, textStyle, iMenuFont); +} + + +static void UI_DrawTier(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + int i; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if (i < 0 || i >= uiInfo.tierCount) { + i = 0; + } + Text_Paint(rect->x, rect->y, scale, color, va("Tier: %s", uiInfo.tierList[i].tierName),0, 0, textStyle, iMenuFont); +} + +static void UI_DrawTierMap(rectDef_t *rect, int index) { + int i; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if (i < 0 || i >= uiInfo.tierCount) { + i = 0; + } + + if (uiInfo.tierList[i].mapHandles[index] == -1) { + uiInfo.tierList[i].mapHandles[index] = trap_R_RegisterShaderNoMip(va("levelshots/%s", uiInfo.tierList[i].maps[index])); + } + + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.tierList[i].mapHandles[index]); +} + +static const char *UI_EnglishMapName(const char *map) { + int i; + for (i = 0; i < uiInfo.mapCount; i++) { + if (Q_stricmp(map, uiInfo.mapList[i].mapLoadName) == 0) { + return uiInfo.mapList[i].mapName; + } + } + return ""; +} + +static void UI_DrawTierMapName(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + int i, j; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if (i < 0 || i >= uiInfo.tierCount) { + i = 0; + } + j = trap_Cvar_VariableValue("ui_currentMap"); + if (j < 0 || j > MAPS_PER_TIER) { + j = 0; + } + + Text_Paint(rect->x, rect->y, scale, color, UI_EnglishMapName(uiInfo.tierList[i].maps[j]), 0, 0, textStyle, iMenuFont); +} + +static void UI_DrawTierGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + int i, j; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if (i < 0 || i >= uiInfo.tierCount) { + i = 0; + } + j = trap_Cvar_VariableValue("ui_currentMap"); + if (j < 0 || j > MAPS_PER_TIER) { + j = 0; + } + + Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[uiInfo.tierList[i].gameTypes[j]].gameType , 0, 0, textStyle,iMenuFont); +} + + +static const char *UI_AIFromName(const char *name) { + int j; + for (j = 0; j < uiInfo.aliasCount; j++) { + if (Q_stricmp(uiInfo.aliasList[j].name, name) == 0) { + return uiInfo.aliasList[j].ai; + } + } + return "Kyle"; +} + + +/* +static qboolean updateOpponentModel = qtrue; +static void UI_DrawOpponent(rectDef_t *rect) { + static playerInfo_t info2; + char model[MAX_QPATH]; + char headmodel[MAX_QPATH]; + char team[256]; + vec3_t viewangles; + vec3_t moveangles; + + if (updateOpponentModel) { + + strcpy(model, UI_Cvar_VariableString("ui_opponentModel")); + strcpy(headmodel, UI_Cvar_VariableString("ui_opponentModel")); + team[0] = '\0'; + + memset( &info2, 0, sizeof(playerInfo_t) ); + viewangles[YAW] = 180 - 10; + viewangles[PITCH] = 0; + viewangles[ROLL] = 0; + VectorClear( moveangles ); + UI_PlayerInfo_SetModel( &info2, model, headmodel, ""); + UI_PlayerInfo_SetInfo( &info2, TORSO_WEAPONREADY3, TORSO_WEAPONREADY3, viewangles, vec3_origin, WP_BRYAR_PISTOL, qfalse ); + UI_RegisterClientModelname( &info2, model, headmodel, team); + updateOpponentModel = qfalse; + } + + UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info2, uiInfo.uiDC.realTime / 2); + +} +*/ +static void UI_NextOpponent() { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + i++; + if (i >= uiInfo.teamCount) { + i = 0; + } + if (i == j) { + i++; + if ( i >= uiInfo.teamCount) { + i = 0; + } + } + trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName ); +} + +static void UI_PriorOpponent() { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + i--; + if (i < 0) { + i = uiInfo.teamCount - 1; + } + if (i == j) { + i--; + if ( i < 0) { + i = uiInfo.teamCount - 1; + } + } + trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName ); +} + +static void UI_DrawPlayerLogo(rectDef_t *rect, vec3_t color) { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + + if (uiInfo.teamList[i].teamIcon == -1) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawPlayerLogoMetal(rectDef_t *rect, vec3_t color) { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + if (uiInfo.teamList[i].teamIcon == -1) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawPlayerLogoName(rectDef_t *rect, vec3_t color) { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + if (uiInfo.teamList[i].teamIcon == -1) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogo(rectDef_t *rect, vec3_t color) { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + if (uiInfo.teamList[i].teamIcon == -1) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogoMetal(rectDef_t *rect, vec3_t color) { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + if (uiInfo.teamList[i].teamIcon == -1) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawOpponentLogoName(rectDef_t *rect, vec3_t color) { + int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + if (uiInfo.teamList[i].teamIcon == -1) { + uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName); + uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName)); + uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName)); + } + + trap_R_SetColor( color ); + UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name ); + trap_R_SetColor( NULL ); +} + +static void UI_DrawAllMapsSelection(rectDef_t *rect, float scale, vec4_t color, int textStyle, qboolean net, int iMenuFont) { + int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer; + if (map >= 0 && map < uiInfo.mapCount) { + Text_Paint(rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle, iMenuFont); + } +} + +static void UI_DrawOpponentName(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_opponentName"), 0, 0, textStyle, iMenuFont); +} + +static int UI_OwnerDrawWidth(int ownerDraw, float scale) { + int i, h, value, findex, iUse = 0; + const char *text; + const char *s = NULL; + + + switch (ownerDraw) { + case UI_HANDICAP: + h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); + i = 20 - h / 5; + s = handicapValues[i]; + break; + case UI_SKIN_COLOR: + switch(uiSkinColor) + { + case TEAM_RED: +// s = "Red"; + s = (char *)UI_GetStringEdString("MENUS", "TEAM_RED"); + break; + case TEAM_BLUE: +// s = "Blue"; + s = (char *)UI_GetStringEdString("MENUS", "TEAM_BLUE"); + break; + default: +// s = "Default"; + s = (char *)UI_GetStringEdString("MENUS", "DEFAULT"); + break; + } + break; + case UI_FORCE_SIDE: + i = uiForceSide; + if (i < 1 || i > 2) { + i = 1; + } + + if (i == FORCE_LIGHTSIDE) + { +// s = "Light"; + s = (char *)UI_GetStringEdString("MENUS", "FORCEDESC_LIGHT"); + } + else + { +// s = "Dark"; + s = (char *)UI_GetStringEdString("MENUS", "FORCEDESC_DARK"); + } + break; + case UI_JEDI_NONJEDI: + i = uiJediNonJedi; + if (i < 0 || i > 1) + { + i = 0; + } + + if (i == FORCE_NONJEDI) + { +// s = "Non-Jedi"; + s = (char *)UI_GetStringEdString("MENUS", "NO"); + } + else + { +// s = "Jedi"; + s = (char *)UI_GetStringEdString("MENUS", "YES"); + } + break; + case UI_FORCE_RANK: + i = uiForceRank; + if (i < 1 || i > MAX_FORCE_RANK) { + i = 1; + } + + s = (char *)UI_GetStringEdString("MP_INGAME", forceMasteryLevels[i]); + break; + case UI_FORCE_RANK_HEAL: + case UI_FORCE_RANK_LEVITATION: + case UI_FORCE_RANK_SPEED: + case UI_FORCE_RANK_PUSH: + case UI_FORCE_RANK_PULL: + case UI_FORCE_RANK_TELEPATHY: + case UI_FORCE_RANK_GRIP: + case UI_FORCE_RANK_LIGHTNING: + case UI_FORCE_RANK_RAGE: + case UI_FORCE_RANK_PROTECT: + case UI_FORCE_RANK_ABSORB: + case UI_FORCE_RANK_TEAM_HEAL: + case UI_FORCE_RANK_TEAM_FORCE: + case UI_FORCE_RANK_DRAIN: + case UI_FORCE_RANK_SEE: + case UI_FORCE_RANK_SABERATTACK: + case UI_FORCE_RANK_SABERDEFEND: + case UI_FORCE_RANK_SABERTHROW: + findex = (ownerDraw - UI_FORCE_RANK)-1; + //this will give us the index as long as UI_FORCE_RANK is always one below the first force rank index + i = uiForcePowersRank[findex]; + + if (i < 0 || i > NUM_FORCE_POWER_LEVELS-1) + { + i = 0; + } + + s = va("%i", uiForcePowersRank[findex]); + break; + case UI_CLANNAME: + s = UI_Cvar_VariableString("ui_teamName"); + break; + case UI_GAMETYPE: + s = uiInfo.gameTypes[ui_gameType.integer].gameType; + break; + case UI_SKILL: + i = trap_Cvar_VariableValue( "g_spSkill" ); + if (i < 1 || i > numSkillLevels) { + i = 1; + } + s = (char *)UI_GetStringEdString("MP_INGAME", (char *)skillLevels[i-1]); + break; + case UI_BLUETEAMNAME: + i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_blueTeam")); + if (i >= 0 && i < uiInfo.teamCount) { + s = va("%s: %s", (char *)UI_GetStringEdString("MENUS", "TEAM_BLUE"), uiInfo.teamList[i].teamName); + } + break; + case UI_REDTEAMNAME: + i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_redTeam")); + if (i >= 0 && i < uiInfo.teamCount) { + s = va("%s: %s", (char *)UI_GetStringEdString("MENUS", "TEAM_RED"), uiInfo.teamList[i].teamName); + } + break; + case UI_BLUETEAM1: + case UI_BLUETEAM2: + case UI_BLUETEAM3: + case UI_BLUETEAM4: + case UI_BLUETEAM5: + case UI_BLUETEAM6: + case UI_BLUETEAM7: + case UI_BLUETEAM8: + if (ownerDraw <= UI_BLUETEAM5) + { + iUse = ownerDraw-UI_BLUETEAM1 + 1; + } + else + { + iUse = ownerDraw-274; //unpleasent hack because I don't want to move up all the UI_BLAHTEAM# defines + } + + value = trap_Cvar_VariableValue(va("ui_blueteam%i", iUse)); + if (value <= 1) { + text = "Human"; + } else { + value -= 2; + if (value >= uiInfo.aliasCount) { + value = 1; + } + text = uiInfo.aliasList[value].name; + } + s = va("%i. %s", iUse, text); + break; + case UI_REDTEAM1: + case UI_REDTEAM2: + case UI_REDTEAM3: + case UI_REDTEAM4: + case UI_REDTEAM5: + case UI_REDTEAM6: + case UI_REDTEAM7: + case UI_REDTEAM8: + if (ownerDraw <= UI_REDTEAM5) + { + iUse = ownerDraw-UI_REDTEAM1 + 1; + } + else + { + iUse = ownerDraw-277; //unpleasent hack because I don't want to move up all the UI_BLAHTEAM# defines + } + + value = trap_Cvar_VariableValue(va("ui_redteam%i", iUse)); + if (value <= 1) { + text = "Human"; + } else { + value -= 2; + if (value >= uiInfo.aliasCount) { + value = 1; + } + text = uiInfo.aliasList[value].name; + } + s = va("%i. %s", iUse, text); + break; + case UI_NETSOURCE: + if (ui_netSource.integer < 0 || ui_netSource.integer > uiInfo.numJoinGameTypes) { + ui_netSource.integer = 0; + } + trap_SP_GetStringTextString("MENUS_SOURCE", holdSPString, sizeof(holdSPString)); + s = va("%s %s", holdSPString, GetNetSourceString(ui_netSource.integer)); + break; + case UI_NETFILTER: + if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) { + ui_serverFilterType.integer = 0; + } + trap_SP_GetStringTextString("MENUS_GAME", holdSPString, sizeof(holdSPString)); + trap_SP_GetStringTextString(serverFilters[ui_serverFilterType.integer].description, holdSPString2, sizeof(holdSPString2)); + + s = va("%s %s", holdSPString, holdSPString2 ); + break; + case UI_TIER: + break; + case UI_TIER_MAPNAME: + break; + case UI_TIER_GAMETYPE: + break; + case UI_ALLMAPS_SELECTION: + break; + case UI_OPPONENT_NAME: + break; + case UI_KEYBINDSTATUS: + if (Display_KeyBindPending()) { + s = UI_GetStringEdString("MP_INGAME", "WAITING_FOR_NEW_KEY"); + } else { + // s = "Press ENTER or CLICK to change, Press BACKSPACE to clear"; + } + break; + 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; +} + +static void UI_DrawBotName(rectDef_t *rect, float scale, vec4_t color, int textStyle,int iMenuFont) +{ + int value = uiInfo.botIndex; + const char *text = ""; + if (value >= UI_GetNumBots()) { + value = 0; + } + text = UI_GetBotNameByNumber(value); + Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle,iMenuFont); +} + +static void UI_DrawBotSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle,int iMenuFont) +{ + if (uiInfo.skillIndex >= 0 && uiInfo.skillIndex < numSkillLevels) + { + Text_Paint(rect->x, rect->y, scale, color, (char *)UI_GetStringEdString("MP_INGAME", (char *)skillLevels[uiInfo.skillIndex]), 0, 0, textStyle,iMenuFont); + } +} + +static void UI_DrawRedBlue(rectDef_t *rect, float scale, vec4_t color, int textStyle,int iMenuFont) +{ + Text_Paint(rect->x, rect->y, scale, color, (uiInfo.redBlue == 0) ? UI_GetStringEdString("MP_INGAME","RED") : UI_GetStringEdString("MP_INGAME","BLUE"), 0, 0, textStyle,iMenuFont); +} + +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_BuildPlayerList +=============== +*/ +static void UI_BuildPlayerList() { + uiClientState_t cs; + int n, count, team, team2, playerTeamNumber; + char info[MAX_INFO_STRING]; + + trap_GetClientState( &cs ); + trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING ); + uiInfo.playerNumber = cs.clientNum; + uiInfo.teamLeader = atoi(Info_ValueForKey(info, "tl")); + team = atoi(Info_ValueForKey(info, "t")); + trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ); + count = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + uiInfo.playerCount = 0; + uiInfo.myTeamCount = 0; + playerTeamNumber = 0; + for( n = 0; n < count; n++ ) { + trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING ); + + if (info[0]) { + Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount], Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); + Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] ); + uiInfo.playerIndexes[uiInfo.playerCount] = n; + uiInfo.playerCount++; + team2 = atoi(Info_ValueForKey(info, "t")); + if (team2 == team && n != uiInfo.playerNumber) { + Q_strncpyz( uiInfo.teamNames[uiInfo.myTeamCount], Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH ); + Q_CleanStr( uiInfo.teamNames[uiInfo.myTeamCount] ); + uiInfo.teamClientNums[uiInfo.myTeamCount] = n; + if (uiInfo.playerNumber == n) { + playerTeamNumber = uiInfo.myTeamCount; + } + uiInfo.myTeamCount++; + } + } + } + + if (!uiInfo.teamLeader) { + trap_Cvar_Set("cg_selectedPlayer", va("%d", playerTeamNumber)); + } + + n = trap_Cvar_VariableValue("cg_selectedPlayer"); + if (n < 0 || n > uiInfo.myTeamCount) { + n = 0; + } + + + if (n < uiInfo.myTeamCount) { + trap_Cvar_Set("cg_selectedPlayerName", uiInfo.teamNames[n]); + } + else + { + trap_Cvar_Set("cg_selectedPlayerName", "Everyone"); + } + + if (!team || team == TEAM_SPECTATOR || !uiInfo.teamLeader) + { + n = uiInfo.myTeamCount; + trap_Cvar_Set("cg_selectedPlayer", va("%d", n)); + trap_Cvar_Set("cg_selectedPlayerName", "N/A"); + } +} + + +static void UI_DrawSelectedPlayer(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) { + if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } + Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("cg_selectedPlayerName"), 0, 0, textStyle, iMenuFont); +} + +static void UI_DrawServerRefreshDate(rectDef_t *rect, float scale, vec4_t color, int textStyle, int iMenuFont) +{ + if (uiInfo.serverStatus.refreshActive) + { + vec4_t lowLight, newColor; + lowLight[0] = 0.8 * color[0]; + lowLight[1] = 0.8 * color[1]; + lowLight[2] = 0.8 * color[2]; + lowLight[3] = 0.8 * color[3]; + LerpColor(color,lowLight,newColor,0.5+0.5*sin((float)(uiInfo.uiDC.realTime / PULSE_DIVISOR))); + + trap_SP_GetStringTextString("MP_INGAME_GETTINGINFOFORSERVERS", holdSPString, sizeof(holdSPString)); + Text_Paint(rect->x, rect->y, scale, newColor, va((char *) holdSPString, trap_LAN_GetServerCount(ui_netSource.integer)), 0, 0, textStyle, iMenuFont); + } + else + { + char buff[64]; + Q_strncpyz(buff, UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)), 64); + trap_SP_GetStringTextString("MP_INGAME_SERVER_REFRESHTIME", holdSPString, sizeof(holdSPString)); + + Text_Paint(rect->x, rect->y, scale, color, va("%s: %s", holdSPString, buff), 0, 0, textStyle, iMenuFont); + } +} + +static void UI_DrawServerMOTD(rectDef_t *rect, float scale, vec4_t color, int iMenuFont) { + if (uiInfo.serverStatus.motdLen) { + float maxX; + + if (uiInfo.serverStatus.motdWidth == -1) { + uiInfo.serverStatus.motdWidth = 0; + uiInfo.serverStatus.motdPaintX = rect->x + 1; + uiInfo.serverStatus.motdPaintX2 = -1; + } + + if (uiInfo.serverStatus.motdOffset > uiInfo.serverStatus.motdLen) { + uiInfo.serverStatus.motdOffset = 0; + uiInfo.serverStatus.motdPaintX = rect->x + 1; + uiInfo.serverStatus.motdPaintX2 = -1; + } + + if (uiInfo.uiDC.realTime > uiInfo.serverStatus.motdTime) { + uiInfo.serverStatus.motdTime = uiInfo.uiDC.realTime + 10; + if (uiInfo.serverStatus.motdPaintX <= rect->x + 2) { + if (uiInfo.serverStatus.motdOffset < uiInfo.serverStatus.motdLen) { + uiInfo.serverStatus.motdPaintX += Text_Width(&uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], scale, 1) - 1; + uiInfo.serverStatus.motdOffset++; + } else { + uiInfo.serverStatus.motdOffset = 0; + if (uiInfo.serverStatus.motdPaintX2 >= 0) { + uiInfo.serverStatus.motdPaintX = uiInfo.serverStatus.motdPaintX2; + } else { + uiInfo.serverStatus.motdPaintX = rect->x + rect->w - 2; + } + uiInfo.serverStatus.motdPaintX2 = -1; + } + } else { + //serverStatus.motdPaintX--; + uiInfo.serverStatus.motdPaintX -= 2; + if (uiInfo.serverStatus.motdPaintX2 >= 0) { + //serverStatus.motdPaintX2--; + uiInfo.serverStatus.motdPaintX2 -= 2; + } + } + } + + maxX = rect->x + rect->w - 2; + Text_Paint_Limit(&maxX, uiInfo.serverStatus.motdPaintX, rect->y + rect->h - 3, scale, color, &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], 0, 0, iMenuFont); + if (uiInfo.serverStatus.motdPaintX2 >= 0) { + float maxX2 = rect->x + rect->w - 2; + Text_Paint_Limit(&maxX2, uiInfo.serverStatus.motdPaintX2, rect->y + rect->h - 3, scale, color, uiInfo.serverStatus.motd, 0, uiInfo.serverStatus.motdOffset, iMenuFont); + } + if (uiInfo.serverStatus.motdOffset && maxX > 0) { + // if we have an offset ( we are skipping the first part of the string ) and we fit the string + if (uiInfo.serverStatus.motdPaintX2 == -1) { + uiInfo.serverStatus.motdPaintX2 = rect->x + rect->w - 2; + } + } else { + uiInfo.serverStatus.motdPaintX2 = -1; + } + + } +} + +static void UI_DrawKeyBindStatus(rectDef_t *rect, float scale, vec4_t color, int textStyle,int iMenuFont) { +// int ofs = 0; TTimo: unused + if (Display_KeyBindPending()) + { + Text_Paint(rect->x, rect->y, scale, color, UI_GetStringEdString("MP_INGAME", "WAITING_FOR_NEW_KEY"), 0, 0, textStyle,iMenuFont); + } else { +// Text_Paint(rect->x, rect->y, scale, color, "Press ENTER or CLICK to change, Press BACKSPACE to clear", 0, 0, textStyle,iMenuFont); + } +} + +static void UI_DrawGLInfo(rectDef_t *rect, float scale, vec4_t color, int textStyle,int iMenuFont) +{ + char * eptr; + char buff[4096]; + const char *lines[128]; + int y, numLines, i; + + Text_Paint(rect->x + 2, rect->y, scale, color, va("GL_VENDOR: %s", uiInfo.uiDC.glconfig.vendor_string), 0, rect->w, textStyle,iMenuFont); + Text_Paint(rect->x + 2, rect->y + 15, scale, color, va("GL_VERSION: %s: %s", uiInfo.uiDC.glconfig.version_string,uiInfo.uiDC.glconfig.renderer_string), 0, rect->w, textStyle,iMenuFont); + Text_Paint(rect->x + 2, rect->y + 30, scale, color, va ("GL_PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)", uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits), 0, rect->w, textStyle,iMenuFont); + + // build null terminated extension strings + Q_strncpyz(buff, uiInfo.uiDC.glconfig.extensions_string, 4096); + eptr = buff; + y = rect->y + 45; + numLines = 0; + while ( y < rect->y + rect->h && *eptr ) + { + while ( *eptr && *eptr == ' ' ) + *eptr++ = '\0'; + + // track start of valid string + if (*eptr && *eptr != ' ') + { + lines[numLines++] = eptr; + } + + while ( *eptr && *eptr != ' ' ) + eptr++; + } + + i = 0; + while (i < numLines) + { + Text_Paint(rect->x + 2, y, scale, color, lines[i++], 0, (rect->w/2), textStyle,iMenuFont); + if (i < numLines) + { + Text_Paint(rect->x + rect->w / 2, y, scale, color, lines[i++], 0, (rect->w/2), textStyle,iMenuFont); + } + y += 10; + if (y > rect->y + rect->h - 11) + { + break; + } + } + + +} + +/* +================= +UI_Version +================= +*/ +static void UI_Version(rectDef_t *rect, float scale, vec4_t color, int iMenuFont) +{ + int width; + + width = uiInfo.uiDC.textWidth(Q3_VERSION, scale, iMenuFont); + + uiInfo.uiDC.drawText(rect->x - width, rect->y, scale, color, Q3_VERSION, 0, 0, 0, iMenuFont); +} + +/* +================= +UI_OwnerDraw +================= +*/ +// FIXME: table drive +// +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 iMenuFont) +{ + rectDef_t rect; + int findex; + int drawRank = 0, iUse = 0; + + rect.x = x + text_x; + rect.y = y + text_y; + rect.w = w; + rect.h = h; + + switch (ownerDraw) + { + case UI_HANDICAP: + UI_DrawHandicap(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_SKIN_COLOR: + UI_DrawSkinColor(&rect, scale, color, textStyle, uiSkinColor, TEAM_FREE, TEAM_BLUE, iMenuFont); + break; + case UI_FORCE_SIDE: + UI_DrawForceSide(&rect, scale, color, textStyle, uiForceSide, 1, 2, iMenuFont); + break; + case UI_JEDI_NONJEDI: + UI_DrawJediNonJedi(&rect, scale, color, textStyle, uiJediNonJedi, 0, 1, iMenuFont); + break; + case UI_FORCE_POINTS: + UI_DrawGenericNum(&rect, scale, color, textStyle, uiForceAvailable, 1, forceMasteryPoints[MAX_FORCE_RANK], ownerDraw,iMenuFont); + break; + case UI_FORCE_MASTERY_SET: + UI_DrawForceMastery(&rect, scale, color, textStyle, uiForceRank, 0, MAX_FORCE_RANK, iMenuFont); + break; + case UI_FORCE_RANK: + UI_DrawForceMastery(&rect, scale, color, textStyle, uiForceRank, 0, MAX_FORCE_RANK, iMenuFont); + break; + case UI_FORCE_RANK_HEAL: + case UI_FORCE_RANK_LEVITATION: + case UI_FORCE_RANK_SPEED: + case UI_FORCE_RANK_PUSH: + case UI_FORCE_RANK_PULL: + case UI_FORCE_RANK_TELEPATHY: + case UI_FORCE_RANK_GRIP: + case UI_FORCE_RANK_LIGHTNING: + case UI_FORCE_RANK_RAGE: + case UI_FORCE_RANK_PROTECT: + case UI_FORCE_RANK_ABSORB: + case UI_FORCE_RANK_TEAM_HEAL: + case UI_FORCE_RANK_TEAM_FORCE: + case UI_FORCE_RANK_DRAIN: + case UI_FORCE_RANK_SEE: + case UI_FORCE_RANK_SABERATTACK: + case UI_FORCE_RANK_SABERDEFEND: + case UI_FORCE_RANK_SABERTHROW: + +// uiForceRank +/* + uiForceUsed + // Only fields for white stars + if (uiForceUsed<3) + { + Menu_ShowItemByName(menu, "lightpowers_team", qtrue); + } + else if (uiForceUsed<6) + { + Menu_ShowItemByName(menu, "lightpowers_team", qtrue); + } +*/ + + findex = (ownerDraw - UI_FORCE_RANK)-1; + //this will give us the index as long as UI_FORCE_RANK is always one below the first force rank index + if (uiForcePowerDarkLight[findex] && uiForceSide != uiForcePowerDarkLight[findex]) + { + color[0] *= 0.5; + color[1] *= 0.5; + color[2] *= 0.5; + } +/* else if (uiForceRank < UI_ForceColorMinRank[bgForcePowerCost[findex][FORCE_LEVEL_1]]) + { + color[0] *= 0.5; + color[1] *= 0.5; + color[2] *= 0.5; + } +*/ drawRank = uiForcePowersRank[findex]; + + UI_DrawForceStars(&rect, scale, color, textStyle, findex, drawRank, 0, NUM_FORCE_POWER_LEVELS-1); + break; + case UI_EFFECTS: + UI_DrawEffects(&rect, scale, color); + break; + case UI_PLAYERMODEL: + //UI_DrawPlayerModel(&rect); + break; + case UI_CLANNAME: + UI_DrawClanName(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_CLANLOGO: + UI_DrawClanLogo(&rect, scale, color); + break; + case UI_CLANCINEMATIC: + UI_DrawClanCinematic(&rect, scale, color); + break; + case UI_PREVIEWCINEMATIC: + UI_DrawPreviewCinematic(&rect, scale, color); + break; + case UI_GAMETYPE: + UI_DrawGameType(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_NETGAMETYPE: + UI_DrawNetGameType(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_AUTOSWITCHLIST: + UI_DrawAutoSwitch(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_JOINGAMETYPE: + UI_DrawJoinGameType(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_MAPPREVIEW: + UI_DrawMapPreview(&rect, scale, color, qtrue); + break; + case UI_MAP_TIMETOBEAT: + UI_DrawMapTimeToBeat(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_MAPCINEMATIC: + UI_DrawMapCinematic(&rect, scale, color, qfalse); + break; + case UI_STARTMAPCINEMATIC: + UI_DrawMapCinematic(&rect, scale, color, qtrue); + break; + case UI_SKILL: + UI_DrawSkill(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_TOTALFORCESTARS: +// UI_DrawTotalForceStars(&rect, scale, color, textStyle); + break; + case UI_BLUETEAMNAME: + UI_DrawTeamName(&rect, scale, color, qtrue, textStyle, iMenuFont); + break; + case UI_REDTEAMNAME: + UI_DrawTeamName(&rect, scale, color, qfalse, textStyle, iMenuFont); + break; + case UI_BLUETEAM1: + case UI_BLUETEAM2: + case UI_BLUETEAM3: + case UI_BLUETEAM4: + case UI_BLUETEAM5: + case UI_BLUETEAM6: + case UI_BLUETEAM7: + case UI_BLUETEAM8: + if (ownerDraw <= UI_BLUETEAM5) + { + iUse = ownerDraw-UI_BLUETEAM1 + 1; + } + else + { + iUse = ownerDraw-274; //unpleasent hack because I don't want to move up all the UI_BLAHTEAM# defines + } + UI_DrawTeamMember(&rect, scale, color, qtrue, iUse, textStyle, iMenuFont); + break; + case UI_REDTEAM1: + case UI_REDTEAM2: + case UI_REDTEAM3: + case UI_REDTEAM4: + case UI_REDTEAM5: + case UI_REDTEAM6: + case UI_REDTEAM7: + case UI_REDTEAM8: + if (ownerDraw <= UI_REDTEAM5) + { + iUse = ownerDraw-UI_REDTEAM1 + 1; + } + else + { + iUse = ownerDraw-277; //unpleasent hack because I don't want to move up all the UI_BLAHTEAM# defines + } + UI_DrawTeamMember(&rect, scale, color, qfalse, iUse, textStyle, iMenuFont); + break; + case UI_NETSOURCE: + UI_DrawNetSource(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_NETMAPPREVIEW: + UI_DrawNetMapPreview(&rect, scale, color); + break; + case UI_NETMAPCINEMATIC: + UI_DrawNetMapCinematic(&rect, scale, color); + break; + case UI_NETFILTER: + UI_DrawNetFilter(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_TIER: + UI_DrawTier(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_OPPONENTMODEL: + //UI_DrawOpponent(&rect); + break; + case UI_TIERMAP1: + UI_DrawTierMap(&rect, 0); + break; + case UI_TIERMAP2: + UI_DrawTierMap(&rect, 1); + break; + case UI_TIERMAP3: + UI_DrawTierMap(&rect, 2); + break; + case UI_PLAYERLOGO: + UI_DrawPlayerLogo(&rect, color); + break; + case UI_PLAYERLOGO_METAL: + UI_DrawPlayerLogoMetal(&rect, color); + break; + case UI_PLAYERLOGO_NAME: + UI_DrawPlayerLogoName(&rect, color); + break; + case UI_OPPONENTLOGO: + UI_DrawOpponentLogo(&rect, color); + break; + case UI_OPPONENTLOGO_METAL: + UI_DrawOpponentLogoMetal(&rect, color); + break; + case UI_OPPONENTLOGO_NAME: + UI_DrawOpponentLogoName(&rect, color); + break; + case UI_TIER_MAPNAME: + UI_DrawTierMapName(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_TIER_GAMETYPE: + UI_DrawTierGameType(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_ALLMAPS_SELECTION: + UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qtrue, iMenuFont); + break; + case UI_MAPS_SELECTION: + UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qfalse, iMenuFont); + break; + case UI_OPPONENT_NAME: + UI_DrawOpponentName(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_BOTNAME: + UI_DrawBotName(&rect, scale, color, textStyle,iMenuFont); + break; + case UI_BOTSKILL: + UI_DrawBotSkill(&rect, scale, color, textStyle,iMenuFont); + break; + case UI_REDBLUE: + UI_DrawRedBlue(&rect, scale, color, textStyle,iMenuFont); + break; + case UI_CROSSHAIR: + UI_DrawCrosshair(&rect, scale, color); + break; + case UI_SELECTEDPLAYER: + UI_DrawSelectedPlayer(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_SERVERREFRESHDATE: + UI_DrawServerRefreshDate(&rect, scale, color, textStyle, iMenuFont); + break; + case UI_SERVERMOTD: + UI_DrawServerMOTD(&rect, scale, color, iMenuFont); + break; + case UI_GLINFO: + UI_DrawGLInfo(&rect,scale, color, textStyle, iMenuFont); + break; + case UI_KEYBINDSTATUS: + UI_DrawKeyBindStatus(&rect,scale, color, textStyle,iMenuFont); + break; + case UI_VERSION: + UI_Version(&rect, scale, color, iMenuFont); + break; + default: + break; + } + +} + +static qboolean UI_OwnerDrawVisible(int flags) { + qboolean vis = qtrue; + + while (flags) { + + if (flags & UI_SHOW_FFA) { + if (trap_Cvar_VariableValue("g_gametype") != GT_FFA && trap_Cvar_VariableValue("g_gametype") != GT_HOLOCRON && trap_Cvar_VariableValue("g_gametype") != GT_JEDIMASTER) { + vis = qfalse; + } + flags &= ~UI_SHOW_FFA; + } + + if (flags & UI_SHOW_NOTFFA) { + if (trap_Cvar_VariableValue("g_gametype") == GT_FFA || trap_Cvar_VariableValue("g_gametype") == GT_HOLOCRON || trap_Cvar_VariableValue("g_gametype") != GT_JEDIMASTER) { + vis = qfalse; + } + flags &= ~UI_SHOW_NOTFFA; + } + + if (flags & UI_SHOW_LEADER) { + // these need to show when this client can give orders to a player or a group + if (!uiInfo.teamLeader) { + vis = qfalse; + } else { + // if showing yourself + if (ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber) { + vis = qfalse; + } + } + flags &= ~UI_SHOW_LEADER; + } + if (flags & UI_SHOW_NOTLEADER) { + // these need to show when this client is assigning their own status or they are NOT the leader + if (uiInfo.teamLeader) { + // if not showing yourself + if (!(ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber)) { + vis = qfalse; + } + // these need to show when this client can give orders to a player or a group + } + flags &= ~UI_SHOW_NOTLEADER; + } + if (flags & UI_SHOW_FAVORITESERVERS) { + // this assumes you only put this type of display flag on something showing in the proper context + if (ui_netSource.integer != AS_FAVORITES) { + vis = qfalse; + } + flags &= ~UI_SHOW_FAVORITESERVERS; + } + if (flags & UI_SHOW_NOTFAVORITESERVERS) { + // this assumes you only put this type of display flag on something showing in the proper context + if (ui_netSource.integer == AS_FAVORITES) { + vis = qfalse; + } + flags &= ~UI_SHOW_NOTFAVORITESERVERS; + } + if (flags & UI_SHOW_ANYTEAMGAME) { + if (uiInfo.gameTypes[ui_gameType.integer].gtEnum <= GT_TEAM ) { + vis = qfalse; + } + flags &= ~UI_SHOW_ANYTEAMGAME; + } + if (flags & UI_SHOW_ANYNONTEAMGAME) { + if (uiInfo.gameTypes[ui_gameType.integer].gtEnum > GT_TEAM ) { + vis = qfalse; + } + flags &= ~UI_SHOW_ANYNONTEAMGAME; + } + if (flags & UI_SHOW_NETANYTEAMGAME) { + if (uiInfo.gameTypes[ui_netGameType.integer].gtEnum <= GT_TEAM ) { + vis = qfalse; + } + flags &= ~UI_SHOW_NETANYTEAMGAME; + } + if (flags & UI_SHOW_NETANYNONTEAMGAME) { + if (uiInfo.gameTypes[ui_netGameType.integer].gtEnum > GT_TEAM ) { + vis = qfalse; + } + flags &= ~UI_SHOW_NETANYNONTEAMGAME; + } + if (flags & UI_SHOW_NEWHIGHSCORE) { + if (uiInfo.newHighScoreTime < uiInfo.uiDC.realTime) { + vis = qfalse; + } else { + if (uiInfo.soundHighScore) { + if (trap_Cvar_VariableValue("sv_killserver") == 0) { + // wait on server to go down before playing sound + //trap_S_StartLocalSound(uiInfo.newHighScoreSound, CHAN_ANNOUNCER); + uiInfo.soundHighScore = qfalse; + } + } + } + flags &= ~UI_SHOW_NEWHIGHSCORE; + } + if (flags & UI_SHOW_NEWBESTTIME) { + if (uiInfo.newBestTime < uiInfo.uiDC.realTime) { + vis = qfalse; + } + flags &= ~UI_SHOW_NEWBESTTIME; + } + if (flags & UI_SHOW_DEMOAVAILABLE) { + if (!uiInfo.demoAvailable) { + vis = qfalse; + } + flags &= ~UI_SHOW_DEMOAVAILABLE; + } else { + flags = 0; + } + } + return vis; +} + +static qboolean UI_Handicap_HandleKey(int flags, float *special, int key) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + int h; + h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") ); + if (key == A_MOUSE2) { + h -= 5; + } else { + h += 5; + } + if (h > 100) { + h = 5; + } else if (h < 0) { + h = 100; + } + trap_Cvar_Set( "handicap", va( "%i", h) ); + return qtrue; + } + return qfalse; +} + +static qboolean UI_Effects_HandleKey(int flags, float *special, int key) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + + if ( !UI_TrueJediEnabled() ) + { + int team = (int)(trap_Cvar_VariableValue("ui_myteam")); + + if (team == TEAM_RED || team==TEAM_BLUE) + { + return qfalse; + } + } + + if (key == A_MOUSE2) { + uiInfo.effectsColor--; + } else { + uiInfo.effectsColor++; + } + + if( uiInfo.effectsColor > 5 ) { + uiInfo.effectsColor = 0; + } else if (uiInfo.effectsColor < 0) { + uiInfo.effectsColor = 5; + } + + trap_Cvar_SetValue( "color1", /*uitogamecode[uiInfo.effectsColor]*/uiInfo.effectsColor ); + return qtrue; + } + return qfalse; +} + +#include "../namespace_begin.h" +extern void Item_RunScript(itemDef_t *item, const char *s); //from ui_shared; +#include "../namespace_end.h" + +// For hot keys on the chat main menu. +static qboolean UI_Chat_Main_HandleKey(int key) +{ + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); + + if (!menu) + { + return (qfalse); + } + + if ((key == A_1) || ( key == A_PLING)) + { + item = Menu_FindItemByName(menu, "attack"); + } + else if ((key == A_2) || ( key == A_AT)) + { + item = Menu_FindItemByName(menu, "defend"); + } + else if ((key == A_3) || ( key == A_HASH)) + { + item = Menu_FindItemByName(menu, "request"); + } + else if ((key == A_4) || ( key == A_STRING)) + { + item = Menu_FindItemByName(menu, "reply"); + } + else if ((key == A_5) || ( key == A_PERCENT)) + { + item = Menu_FindItemByName(menu, "spot"); + } + else if ((key == A_6) || ( key == A_CARET)) + { + item = Menu_FindItemByName(menu, "tactics"); + } + else + { + return (qfalse); + } + + if (item) + { + Item_RunScript(item, item->action); + } + + return (qtrue); +} + +// For hot keys on the chat main menu. +static qboolean UI_Chat_Attack_HandleKey(int key) +{ + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); + + if (!menu) + { + return (qfalse); + } + + if ((key == A_1) || ( key == A_PLING)) + { + item = Menu_FindItemByName(menu, "att_01"); + } + else if ((key == A_2) || ( key == A_AT)) + { + item = Menu_FindItemByName(menu, "att_02"); + } + else if ((key == A_3) || ( key == A_HASH)) + { + item = Menu_FindItemByName(menu, "att_03"); + } + else + { + return (qfalse); + } + + if (item) + { + Item_RunScript(item, item->action); + } + + return (qtrue); +} + +// For hot keys on the chat main menu. +static qboolean UI_Chat_Defend_HandleKey(int key) +{ + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); + + if (!menu) + { + return (qfalse); + } + + if ((key == A_1) || ( key == A_PLING)) + { + item = Menu_FindItemByName(menu, "def_01"); + } + else if ((key == A_2) || ( key == A_AT)) + { + item = Menu_FindItemByName(menu, "def_02"); + } + else if ((key == A_3) || ( key == A_HASH)) + { + item = Menu_FindItemByName(menu, "def_03"); + } + else if ((key == A_4) || ( key == A_STRING)) + { + item = Menu_FindItemByName(menu, "def_04"); + } + else + { + return (qfalse); + } + + if (item) + { + Item_RunScript(item, item->action); + } + + return (qtrue); +} + +// For hot keys on the chat main menu. +static qboolean UI_Chat_Request_HandleKey(int key) +{ + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); + + if (!menu) + { + return (qfalse); + } + + if ((key == A_1) || ( key == A_PLING)) + { + item = Menu_FindItemByName(menu, "req_01"); + } + else if ((key == A_2) || ( key == A_AT)) + { + item = Menu_FindItemByName(menu, "req_02"); + } + else if ((key == A_3) || ( key == A_HASH)) + { + item = Menu_FindItemByName(menu, "req_03"); + } + else if ((key == A_4) || ( key == A_STRING)) + { + item = Menu_FindItemByName(menu, "req_04"); + } + else if ((key == A_5) || ( key == A_PERCENT)) + { + item = Menu_FindItemByName(menu, "req_05"); + } + else if ((key == A_6) || ( key == A_CARET)) + { + item = Menu_FindItemByName(menu, "req_06"); + } + else + { + return (qfalse); + } + + if (item) + { + Item_RunScript(item, item->action); + } + + return (qtrue); +} + +// For hot keys on the chat main menu. +static qboolean UI_Chat_Reply_HandleKey(int key) +{ + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); + + if (!menu) + { + return (qfalse); + } + + if ((key == A_1) || ( key == A_PLING)) + { + item = Menu_FindItemByName(menu, "rep_01"); + } + else if ((key == A_2) || ( key == A_AT)) + { + item = Menu_FindItemByName(menu, "rep_02"); + } + else if ((key == A_3) || ( key == A_HASH)) + { + item = Menu_FindItemByName(menu, "rep_03"); + } + else if ((key == A_4) || ( key == A_STRING)) + { + item = Menu_FindItemByName(menu, "rep_04"); + } + else if ((key == A_5) || ( key == A_PERCENT)) + { + item = Menu_FindItemByName(menu, "rep_05"); + } + else + { + return (qfalse); + } + + if (item) + { + Item_RunScript(item, item->action); + } + + return (qtrue); +} + +// For hot keys on the chat main menu. +static qboolean UI_Chat_Spot_HandleKey(int key) +{ + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); + + if (!menu) + { + return (qfalse); + } + + if ((key == A_1) || ( key == A_PLING)) + { + item = Menu_FindItemByName(menu, "spot_01"); + } + else if ((key == A_2) || ( key == A_AT)) + { + item = Menu_FindItemByName(menu, "spot_02"); + } + else if ((key == A_3) || ( key == A_HASH)) + { + item = Menu_FindItemByName(menu, "spot_03"); + } + else if ((key == A_4) || ( key == A_STRING)) + { + item = Menu_FindItemByName(menu, "spot_04"); + } + else + { + return (qfalse); + } + + if (item) + { + Item_RunScript(item, item->action); + } + + return (qtrue); +} + +// For hot keys on the chat main menu. +static qboolean UI_Chat_Tactical_HandleKey(int key) +{ + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); + + if (!menu) + { + return (qfalse); + } + + if ((key == A_1) || ( key == A_PLING)) + { + item = Menu_FindItemByName(menu, "tac_01"); + } + else if ((key == A_2) || ( key == A_AT)) + { + item = Menu_FindItemByName(menu, "tac_02"); + } + else if ((key == A_3) || ( key == A_HASH)) + { + item = Menu_FindItemByName(menu, "tac_03"); + } + else if ((key == A_4) || ( key == A_STRING)) + { + item = Menu_FindItemByName(menu, "tac_04"); + } + else if ((key == A_5) || ( key == A_PERCENT)) + { + item = Menu_FindItemByName(menu, "tac_05"); + } + else if ((key == A_6) || ( key == A_CARET)) + { + item = Menu_FindItemByName(menu, "tac_06"); + } + else + { + return (qfalse); + } + + if (item) + { + Item_RunScript(item, item->action); + } + + return (qtrue); +} + +static qboolean UI_GameType_HandleKey(int flags, float *special, int key, qboolean resetMap) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + int oldCount = UI_MapCountByGameType(qtrue); + + // hard coded mess here + if (key == A_MOUSE2) { + ui_gameType.integer--; + if (ui_gameType.integer == 2) { + ui_gameType.integer = 1; + } else if (ui_gameType.integer < 2) { + ui_gameType.integer = uiInfo.numGameTypes - 1; + } + } else { + ui_gameType.integer++; + if (ui_gameType.integer >= uiInfo.numGameTypes) { + ui_gameType.integer = 1; + } else if (ui_gameType.integer == 2) { + ui_gameType.integer = 3; + } + } + + trap_Cvar_Set("ui_gameType", va("%d", ui_gameType.integer)); + UI_SetCapFragLimits(qtrue); + UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); + if (resetMap && oldCount != UI_MapCountByGameType(qtrue)) { + trap_Cvar_Set( "ui_currentMap", "0"); + Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, NULL); + } + return qtrue; + } + return qfalse; +} + +// If we're in the solo menu, don't let them see siege maps. +static qboolean UI_InSoloMenu( void ) +{ + menuDef_t *menu; + itemDef_t *item; + char *name = "solo_gametypefield"; + + menu = Menu_GetFocused(); // Get current menu (either video or ingame video, I would assume) + + if (!menu) + { + return (qfalse); + } + + item = Menu_FindItemByName(menu, name); + if (item) + { + return qtrue; + } + + return (qfalse); +} + +static qboolean UI_NetGameType_HandleKey(int flags, float *special, int key) +{ +#ifdef _XBOX + if (key == A_CURSOR_RIGHT || key == A_CURSOR_LEFT) +#else + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) +#endif + { + +#ifdef _XBOX + if (key == A_CURSOR_LEFT) +#else + if (key == A_MOUSE2) +#endif + { + ui_netGameType.integer--; + if (UI_InSoloMenu()) + { + if (uiInfo.gameTypes[ui_netGameType.integer].gtEnum == GT_SIEGE) + { + ui_netGameType.integer--; + } + } + } + else + { + ui_netGameType.integer++; + if (UI_InSoloMenu()) + { + if (uiInfo.gameTypes[ui_netGameType.integer].gtEnum == GT_SIEGE) + { + ui_netGameType.integer++; + } + } + } + + if (ui_netGameType.integer < 0) + { + ui_netGameType.integer = uiInfo.numGameTypes - 1; + } + else if (ui_netGameType.integer >= uiInfo.numGameTypes) + { + ui_netGameType.integer = 0; + } + + trap_Cvar_Set( "ui_netGameType", va("%d", ui_netGameType.integer)); + trap_Cvar_Set( "ui_actualnetGameType", va("%d", uiInfo.gameTypes[ui_netGameType.integer].gtEnum)); + trap_Cvar_Set( "ui_currentNetMap", "0"); + UI_MapCountByGameType(qfalse); + Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, NULL); + return qtrue; + } + return qfalse; +} + +static qboolean UI_AutoSwitch_HandleKey(int flags, float *special, int key) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + int switchVal = trap_Cvar_VariableValue("cg_autoswitch"); + + if (key == A_MOUSE2) { + switchVal--; + } else { + switchVal++; + } + + if (switchVal < 0) + { + switchVal = 2; + } + else if (switchVal >= 3) + { + switchVal = 0; + } + + trap_Cvar_Set( "cg_autoswitch", va("%i", switchVal)); + return qtrue; + } + return qfalse; +} + +static qboolean UI_JoinGameType_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) { + ui_joinGameType.integer--; + } else { + ui_joinGameType.integer++; + } + + if (ui_joinGameType.integer < 0) { + ui_joinGameType.integer = uiInfo.numJoinGameTypes - 1; + } else if (ui_joinGameType.integer >= uiInfo.numJoinGameTypes) { + ui_joinGameType.integer = 0; + } + + trap_Cvar_Set( "ui_joinGameType", va("%d", ui_joinGameType.integer)); + UI_BuildServerDisplayList(qtrue); + return qtrue; + } + return qfalse; +} + + + +static qboolean UI_Skill_HandleKey(int flags, float *special, int key) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + int i = trap_Cvar_VariableValue( "g_spSkill" ); + + if (key == A_MOUSE2) { + i--; + } else { + i++; + } + + if (i < 1) { + i = numSkillLevels; + } else if (i > numSkillLevels) { + i = 1; + } + + trap_Cvar_Set("g_spSkill", va("%i", i)); + return qtrue; + } + return qfalse; +} + + +static qboolean UI_TeamName_HandleKey(int flags, float *special, int key, qboolean blue) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + int i; + i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam")); + + if (key == A_MOUSE2) { + i--; + } else { + i++; + } + + if (i >= uiInfo.teamCount) { + i = 0; + } else if (i < 0) { + i = uiInfo.teamCount - 1; + } + + trap_Cvar_Set( (blue) ? "ui_blueTeam" : "ui_redTeam", uiInfo.teamList[i].teamName); + + return qtrue; + } + return qfalse; +} + +static qboolean UI_TeamMember_HandleKey(int flags, float *special, int key, qboolean blue, int num) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + // 0 - None + // 1 - Human + // 2..NumCharacters - Bot + char *cvar = va(blue ? "ui_blueteam%i" : "ui_redteam%i", num); + int value = trap_Cvar_VariableValue(cvar); + int maxcl = trap_Cvar_VariableValue( "sv_maxClients" ); + int numval = num; + + numval *= 2; + + if (blue) + { + numval -= 1; + } + + if (numval > maxcl) + { + return qfalse; + } + + if (value < 1) + { + value = 1; + } + + if (key == A_MOUSE2) { + value--; + } else { + value++; + } + + /*if (ui_actualNetGameType.integer >= GT_TEAM) { + if (value >= uiInfo.characterCount + 2) { + value = 0; + } else if (value < 0) { + value = uiInfo.characterCount + 2 - 1; + } + } else {*/ + if (value >= UI_GetNumBots() + 2) { + value = 1; + } else if (value < 1) { + value = UI_GetNumBots() + 2 - 1; + } + //} + + trap_Cvar_Set(cvar, va("%i", value)); + return qtrue; + } + return qfalse; +} + +static qboolean UI_NetSource_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) { + ui_netSource.integer--; + } else { + ui_netSource.integer++; + } + + if (ui_netSource.integer >= numNetSources) { + ui_netSource.integer = 0; + } else if (ui_netSource.integer < 0) { + ui_netSource.integer = numNetSources - 1; + } + + UI_BuildServerDisplayList(qtrue); + if (ui_netSource.integer != AS_GLOBAL) { + UI_StartServerRefresh(qtrue); + } + trap_Cvar_Set( "ui_netSource", va("%d", ui_netSource.integer)); + return qtrue; + } + return qfalse; +} + +static qboolean UI_NetFilter_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) { + ui_serverFilterType.integer--; + } else { + ui_serverFilterType.integer++; + } + + if (ui_serverFilterType.integer >= numServerFilters) { + ui_serverFilterType.integer = 0; + } else if (ui_serverFilterType.integer < 0) { + ui_serverFilterType.integer = numServerFilters - 1; + } + UI_BuildServerDisplayList(qtrue); + return qtrue; + } + return qfalse; +} + +static qboolean UI_OpponentName_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) { + UI_PriorOpponent(); + } else { + UI_NextOpponent(); + } + return qtrue; + } + return qfalse; +} + +static qboolean UI_BotName_HandleKey(int flags, float *special, int key) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { +// int game = trap_Cvar_VariableValue("g_gametype"); + int value = uiInfo.botIndex; + + if (key == A_MOUSE2) { + value--; + } else { + value++; + } + + /* + if (game >= GT_TEAM) { + if (value >= uiInfo.characterCount + 2) { + value = 0; + } else if (value < 0) { + value = uiInfo.characterCount + 2 - 1; + } + } else { + */ + if (value >= UI_GetNumBots()/* + 2*/) { + value = 0; + } else if (value < 0) { + value = UI_GetNumBots()/* + 2*/ - 1; + } + //} + uiInfo.botIndex = value; + return qtrue; + } + return qfalse; +} + +static qboolean UI_BotSkill_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.skillIndex--; + } else { + uiInfo.skillIndex++; + } + if (uiInfo.skillIndex >= numSkillLevels) { + uiInfo.skillIndex = 0; + } else if (uiInfo.skillIndex < 0) { + uiInfo.skillIndex = numSkillLevels-1; + } + return qtrue; + } + return qfalse; +} + +static qboolean UI_RedBlue_HandleKey(int flags, float *special, int key) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + uiInfo.redBlue ^= 1; + return qtrue; + } + return qfalse; +} + +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; + } + trap_Cvar_Set("cg_drawCrosshair", va("%d", uiInfo.currentCrosshair)); + return qtrue; + } + return qfalse; +} + + + +static qboolean UI_SelectedPlayer_HandleKey(int flags, float *special, int key) { + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER || key == A_KP_ENTER) { + int selected; + + UI_BuildPlayerList(); + if (!uiInfo.teamLeader) { + return qfalse; + } + selected = trap_Cvar_VariableValue("cg_selectedPlayer"); + + if (key == A_MOUSE2) { + selected--; + } else { + selected++; + } + + if (selected > uiInfo.myTeamCount) { + selected = 0; + } else if (selected < 0) { + selected = uiInfo.myTeamCount; + } + + if (selected == uiInfo.myTeamCount) { + trap_Cvar_Set( "cg_selectedPlayerName", "Everyone"); + } else { + trap_Cvar_Set( "cg_selectedPlayerName", uiInfo.teamNames[selected]); + } + trap_Cvar_Set( "cg_selectedPlayer", va("%d", selected)); + } + return qfalse; +} + +/* +static qboolean UI_VoiceChat_HandleKey(int flags, float *special, int key) +{ + + qboolean ret = qfalse; + + switch(key) + { + case A_1: + case A_KP_1: + ret = qtrue; + break; + case A_2: + case A_KP_2: + ret = qtrue; + break; + + } + + return ret; +} +*/ + + +#ifdef _XBOX +static qboolean UI_XboxPasscode_HandleKey(int flags, float *special, int key) +{ + static BYTE passcode[XONLINE_PASSCODE_LENGTH]; + int passcodeState = trap_Cvar_VariableValue( "xb_passcodeState" ); + + // If the user hasn't entered a full passcode yet + if (passcodeState >= 0 && passcodeState <= 3) + { + switch (key) + { + // Undo our stupid UI joy2key mapping that was done in the input system + case A_CURSOR_DOWN: + passcode[passcodeState++] = XONLINE_PASSCODE_DPAD_DOWN; break; + case A_CURSOR_LEFT: + passcode[passcodeState++] = XONLINE_PASSCODE_DPAD_LEFT; break; + case A_CURSOR_RIGHT: + passcode[passcodeState++] = XONLINE_PASSCODE_DPAD_RIGHT; break; + case A_CURSOR_UP: + passcode[passcodeState++] = XONLINE_PASSCODE_DPAD_UP; break; + case A_PAGE_UP: + passcode[passcodeState++] = XONLINE_PASSCODE_GAMEPAD_LEFT_TRIGGER; break; + case A_PAGE_DOWN: + passcode[passcodeState++] = XONLINE_PASSCODE_GAMEPAD_RIGHT_TRIGGER; break; + case A_DELETE: + passcode[passcodeState++] = XONLINE_PASSCODE_GAMEPAD_X; break; + case A_BACKSPACE: + passcode[passcodeState++] = XONLINE_PASSCODE_GAMEPAD_Y; break; + default: + // No other button (including "A") does anything here + return qfalse; + } + + // User has incremented passcodeState - change the cvar, and we're done + trap_Cvar_Set( "xb_passcodeState", va("%d", passcodeState) ); + return qtrue; + } + + // If the user has a full passcode on screen: + if (passcodeState == 4) + { + // Pressing "A" tests the code. Every other button does nothing + if (key != A_MOUSE1) + return qfalse; + + // Test the passcode + XONLINE_USER *pUser = XBL_GetUserInfo( XBL_GetSelectedAccountIndex() ); + if (memcmp(pUser->passcode, passcode, sizeof(passcode)) == 0) + { + // Success - resume logging in + Menus_CloseByName( "xbox_passcode" ); + XBL_Login( LOGIN_CONNECT ); + return qtrue; + } + else + { + // Wrong - set state to invalid - so menu changes + trap_Cvar_Set( "xb_passcodeState", "5" ); + return qtrue; + } + } + + // If the user had already entered an invalid passcode + if (passcodeState == 5) + { + // Pressing "A" brings them back to the beginning, to try again. + // All other buttons do nothing + if (key != A_MOUSE1) + return qfalse; + + trap_Cvar_Set( "xb_passcodeState", "0" ); + return qtrue; + } + + // No other state is valid! + assert( 0 ); + return qfalse; +} +#endif + + +static qboolean UI_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { + int findex, iUse = 0; + + switch (ownerDraw) { + case UI_HANDICAP: + return UI_Handicap_HandleKey(flags, special, key); + break; + case UI_SKIN_COLOR: + return UI_SkinColor_HandleKey(flags, special, key, uiSkinColor, TEAM_FREE, TEAM_BLUE, ownerDraw); + break; + case UI_FORCE_SIDE: + return UI_ForceSide_HandleKey(flags, special, key, uiForceSide, 1, 2, ownerDraw); + break; + case UI_JEDI_NONJEDI: + return UI_JediNonJedi_HandleKey(flags, special, key, uiJediNonJedi, 0, 1, ownerDraw); + break; + case UI_FORCE_MASTERY_SET: + return UI_ForceMaxRank_HandleKey(flags, special, key, uiForceRank, 1, MAX_FORCE_RANK, ownerDraw); + break; + case UI_FORCE_RANK: + break; + case UI_CHAT_MAIN: + return UI_Chat_Main_HandleKey(key); + break; + case UI_CHAT_ATTACK: + return UI_Chat_Attack_HandleKey(key); + break; + case UI_CHAT_DEFEND: + return UI_Chat_Defend_HandleKey(key); + break; + case UI_CHAT_REQUEST: + return UI_Chat_Request_HandleKey(key); + break; + case UI_CHAT_REPLY: + return UI_Chat_Reply_HandleKey(key); + break; + case UI_CHAT_SPOT: + return UI_Chat_Spot_HandleKey(key); + break; + case UI_CHAT_TACTICAL: + return UI_Chat_Tactical_HandleKey(key); + break; + case UI_FORCE_RANK_HEAL: + case UI_FORCE_RANK_LEVITATION: + case UI_FORCE_RANK_SPEED: + case UI_FORCE_RANK_PUSH: + case UI_FORCE_RANK_PULL: + case UI_FORCE_RANK_TELEPATHY: + case UI_FORCE_RANK_GRIP: + case UI_FORCE_RANK_LIGHTNING: + case UI_FORCE_RANK_RAGE: + case UI_FORCE_RANK_PROTECT: + case UI_FORCE_RANK_ABSORB: + case UI_FORCE_RANK_TEAM_HEAL: + case UI_FORCE_RANK_TEAM_FORCE: + case UI_FORCE_RANK_DRAIN: + case UI_FORCE_RANK_SEE: + case UI_FORCE_RANK_SABERATTACK: + case UI_FORCE_RANK_SABERDEFEND: + case UI_FORCE_RANK_SABERTHROW: + findex = (ownerDraw - UI_FORCE_RANK)-1; + //this will give us the index as long as UI_FORCE_RANK is always one below the first force rank index + return UI_ForcePowerRank_HandleKey(flags, special, key, uiForcePowersRank[findex], 0, NUM_FORCE_POWER_LEVELS-1, ownerDraw); + break; + case UI_EFFECTS: + return UI_Effects_HandleKey(flags, special, key); + break; + case UI_GAMETYPE: + return UI_GameType_HandleKey(flags, special, key, qtrue); + break; + case UI_NETGAMETYPE: + return UI_NetGameType_HandleKey(flags, special, key); + break; + case UI_AUTOSWITCHLIST: + return UI_AutoSwitch_HandleKey(flags, special, key); + break; + case UI_JOINGAMETYPE: + return UI_JoinGameType_HandleKey(flags, special, key); + break; + case UI_SKILL: + return UI_Skill_HandleKey(flags, special, key); + break; + case UI_BLUETEAMNAME: + return UI_TeamName_HandleKey(flags, special, key, qtrue); + break; + case UI_REDTEAMNAME: + return UI_TeamName_HandleKey(flags, special, key, qfalse); + break; + case UI_BLUETEAM1: + case UI_BLUETEAM2: + case UI_BLUETEAM3: + case UI_BLUETEAM4: + case UI_BLUETEAM5: + case UI_BLUETEAM6: + case UI_BLUETEAM7: + case UI_BLUETEAM8: + if (ownerDraw <= UI_BLUETEAM5) + { + iUse = ownerDraw-UI_BLUETEAM1 + 1; + } + else + { + iUse = ownerDraw-274; //unpleasent hack because I don't want to move up all the UI_BLAHTEAM# defines + } + + UI_TeamMember_HandleKey(flags, special, key, qtrue, iUse); + break; + case UI_REDTEAM1: + case UI_REDTEAM2: + case UI_REDTEAM3: + case UI_REDTEAM4: + case UI_REDTEAM5: + case UI_REDTEAM6: + case UI_REDTEAM7: + case UI_REDTEAM8: + if (ownerDraw <= UI_REDTEAM5) + { + iUse = ownerDraw-UI_REDTEAM1 + 1; + } + else + { + iUse = ownerDraw-277; //unpleasent hack because I don't want to move up all the UI_BLAHTEAM# defines + } + UI_TeamMember_HandleKey(flags, special, key, qfalse, iUse); + break; + case UI_NETSOURCE: + UI_NetSource_HandleKey(flags, special, key); + break; + case UI_NETFILTER: + UI_NetFilter_HandleKey(flags, special, key); + break; + case UI_OPPONENT_NAME: + UI_OpponentName_HandleKey(flags, special, key); + break; + case UI_BOTNAME: + return UI_BotName_HandleKey(flags, special, key); + break; + case UI_BOTSKILL: + return UI_BotSkill_HandleKey(flags, special, key); + break; + case UI_REDBLUE: + UI_RedBlue_HandleKey(flags, special, key); + break; + case UI_CROSSHAIR: + UI_Crosshair_HandleKey(flags, special, key); + break; + case UI_SELECTEDPLAYER: + UI_SelectedPlayer_HandleKey(flags, special, key); + break; + // case UI_VOICECHAT: + // UI_VoiceChat_HandleKey(flags, special, key); + // break; +#ifdef _XBOX + case UI_XBOX_PASSCODE: + UI_XboxPasscode_HandleKey(flags, special, key); + break; +#endif + default: + break; + } + + return qfalse; +} + + +static float UI_GetValue(int ownerDraw) { + return 0; +} + +/* +================= +UI_ServersQsortCompare +================= +*/ +static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ) { + return trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, *(int*)arg1, *(int*)arg2); +} + + +/* +================= +UI_ServersSort +================= +*/ +void UI_ServersSort(int column, qboolean force) { + + if ( !force ) { + if ( uiInfo.serverStatus.sortKey == column ) { + return; + } + } + + uiInfo.serverStatus.sortKey = column; + qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof(int), UI_ServersQsortCompare); +} + +/* +static void UI_StartSinglePlayer() { + int i,j, k, skill; + char buff[1024]; + i = trap_Cvar_VariableValue( "ui_currentTier" ); + if (i < 0 || i >= tierCount) { + i = 0; + } + j = trap_Cvar_VariableValue("ui_currentMap"); + if (j < 0 || j > MAPS_PER_TIER) { + j = 0; + } + + trap_Cvar_SetValue( "singleplayer", 1 ); + trap_Cvar_SetValue( "g_gametype", Com_Clamp( 0, 7, tierList[i].gameTypes[j] ) ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", tierList[i].maps[j] ) ); + skill = trap_Cvar_VariableValue( "g_spSkill" ); + + if (j == MAPS_PER_TIER-1) { + k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + Com_sprintf( buff, sizeof(buff), "wait ; addbot %s %i %s 250 %s\n", UI_AIFromName(teamList[k].teamMembers[0]), skill, "", teamList[k].teamMembers[0]); + } else { + k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + for (i = 0; i < PLAYERS_PER_TEAM; i++) { + Com_sprintf( buff, sizeof(buff), "wait ; addbot %s %i %s 250 %s\n", UI_AIFromName(teamList[k].teamMembers[i]), skill, "Blue", teamList[k].teamMembers[i]); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + } + + k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + for (i = 1; i < PLAYERS_PER_TEAM; i++) { + Com_sprintf( buff, sizeof(buff), "wait ; addbot %s %i %s 250 %s\n", UI_AIFromName(teamList[k].teamMembers[i]), skill, "Red", teamList[k].teamMembers[i]); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + } + trap_Cmd_ExecuteText( EXEC_APPEND, "wait 5; team Red\n" ); + } + + +} +*/ + +/* +=============== +UI_LoadMods +=============== +*/ +static void UI_LoadMods() { + int numdirs; + char dirlist[2048]; + char *dirptr; + char *descptr; + int i; + int dirlen; + + uiInfo.modCount = 0; + numdirs = trap_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; + } + } + +} + + +/* +=============== +UI_LoadMovies +=============== +*/ +static void UI_LoadMovies() { + char movielist[4096]; + char *moviename; + int i, len; + + uiInfo.movieCount = trap_FS_GetFileList( "video", "roq", movielist, 4096 ); + + if (uiInfo.movieCount) { + if (uiInfo.movieCount > MAX_MOVIES) { + uiInfo.movieCount = MAX_MOVIES; + } + moviename = movielist; + for ( i = 0; i < uiInfo.movieCount; i++ ) { + len = strlen( moviename ); + if (!Q_stricmp(moviename + len - 4,".roq")) { + moviename[len-4] = '\0'; + } + Q_strupr(moviename); + uiInfo.movieList[i] = String_Alloc(moviename); + moviename += len + 1; + } + } + +} + + + +/* +=============== +UI_LoadDemos +=============== +*/ +static void UI_LoadDemos() { + char demolist[4096]; + char demoExt[32]; + char *demoname; + int i, len; + + Com_sprintf(demoExt, sizeof(demoExt), "dm_%d", (int)trap_Cvar_VariableValue("protocol")); + + uiInfo.demoCount = trap_FS_GetFileList( "demos", demoExt, demolist, 4096 ); + + Com_sprintf(demoExt, sizeof(demoExt), ".dm_%d", (int)trap_Cvar_VariableValue("protocol")); + + if (uiInfo.demoCount) { + if (uiInfo.demoCount > MAX_DEMOS) { + uiInfo.demoCount = MAX_DEMOS; + } + demoname = demolist; + for ( i = 0; i < uiInfo.demoCount; i++ ) { + len = strlen( demoname ); + if (!Q_stricmp(demoname + len - strlen(demoExt), demoExt)) { + demoname[len-strlen(demoExt)] = '\0'; + } + Q_strupr(demoname); + uiInfo.demoList[i] = String_Alloc(demoname); + demoname += len + 1; + } + } + +} + + +static qboolean UI_SetNextMap(int actual, int index) { + int i; + for (i = actual + 1; i < uiInfo.mapCount; i++) { + if (uiInfo.mapList[i].active) { + Menu_SetFeederSelection(NULL, FEEDER_MAPS, index + 1, "skirmish"); + return qtrue; + } + } + return qfalse; +} + + +static void UI_StartSkirmish(qboolean next) { + int i, k, g, delay, temp; + float skill; + char buff[MAX_STRING_CHARS]; + + temp = trap_Cvar_VariableValue( "g_gametype" ); + trap_Cvar_Set("ui_gameType", va("%i", temp)); + + if (next) { + int actual; + int index = trap_Cvar_VariableValue("ui_mapIndex"); + UI_MapCountByGameType(qtrue); + UI_SelectedMap(index, &actual); + if (UI_SetNextMap(actual, index)) { + } else { + UI_GameType_HandleKey(0, 0, A_MOUSE1, qfalse); + UI_MapCountByGameType(qtrue); + Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, "skirmish"); + } + } + + g = uiInfo.gameTypes[ui_gameType.integer].gtEnum; + trap_Cvar_SetValue( "g_gametype", g ); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName) ); + skill = trap_Cvar_VariableValue( "g_spSkill" ); + trap_Cvar_Set("ui_scoreMap", uiInfo.mapList[ui_currentMap.integer].mapName); + + k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName")); + + trap_Cvar_Set("ui_singlePlayerActive", "1"); + + // set up sp overrides, will be replaced on postgame + temp = trap_Cvar_VariableValue( "capturelimit" ); + trap_Cvar_Set("ui_saveCaptureLimit", va("%i", temp)); + temp = trap_Cvar_VariableValue( "fraglimit" ); + trap_Cvar_Set("ui_saveFragLimit", va("%i", temp)); + temp = trap_Cvar_VariableValue( "duel_fraglimit" ); + trap_Cvar_Set("ui_saveDuelLimit", va("%i", temp)); + + UI_SetCapFragLimits(qfalse); + + temp = trap_Cvar_VariableValue( "cg_drawTimer" ); + trap_Cvar_Set("ui_drawTimer", va("%i", temp)); + temp = trap_Cvar_VariableValue( "g_doWarmup" ); + trap_Cvar_Set("ui_doWarmup", va("%i", temp)); + temp = trap_Cvar_VariableValue( "g_friendlyFire" ); + trap_Cvar_Set("ui_friendlyFire", va("%i", temp)); + temp = trap_Cvar_VariableValue( "sv_maxClients" ); + trap_Cvar_Set("ui_maxClients", va("%i", temp)); + temp = trap_Cvar_VariableValue( "g_warmup" ); + trap_Cvar_Set("ui_Warmup", va("%i", temp)); + temp = trap_Cvar_VariableValue( "sv_pure" ); + trap_Cvar_Set("ui_pure", va("%i", temp)); + + trap_Cvar_Set("cg_cameraOrbit", "0"); + trap_Cvar_Set("cg_thirdPerson", "0"); + trap_Cvar_Set("cg_drawTimer", "1"); + trap_Cvar_Set("g_doWarmup", "1"); + trap_Cvar_Set("g_warmup", "15"); + trap_Cvar_Set("sv_pure", "0"); + trap_Cvar_Set("g_friendlyFire", "0"); + trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName")); + trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName")); + + if (trap_Cvar_VariableValue("ui_recordSPDemo")) { + Com_sprintf(buff, MAX_STRING_CHARS, "%s_%i", uiInfo.mapList[ui_currentMap.integer].mapLoadName, g); + trap_Cvar_Set("ui_recordSPDemoName", buff); + } + + delay = 500; + + if (g == GT_DUEL || g == GT_POWERDUEL) { + temp = uiInfo.mapList[ui_currentMap.integer].teamMembers * 2; + trap_Cvar_Set("sv_maxClients", va("%d", temp)); + Com_sprintf( buff, sizeof(buff), "wait ; addbot %s %f "", %i \n", uiInfo.mapList[ui_currentMap.integer].opponentName, skill, delay); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + } else if (g == GT_HOLOCRON || g == GT_JEDIMASTER) { + temp = uiInfo.mapList[ui_currentMap.integer].teamMembers * 2; + trap_Cvar_Set("sv_maxClients", va("%d", temp)); + for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers; i++) { + Com_sprintf( buff, sizeof(buff), "addbot \"%s\" %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, (g == GT_HOLOCRON) ? "" : "Blue", delay, uiInfo.teamList[k].teamMembers[i]); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + delay += 500; + } + k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers-1; i++) { + Com_sprintf( buff, sizeof(buff), "addbot \"%s\" %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, (g == GT_HOLOCRON) ? "" : "Red", delay, uiInfo.teamList[k].teamMembers[i]); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + delay += 500; + } + } else { + temp = uiInfo.mapList[ui_currentMap.integer].teamMembers * 2; + trap_Cvar_Set("sv_maxClients", va("%d", temp)); + for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers; i++) { + Com_sprintf( buff, sizeof(buff), "addbot \"%s\" %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, (g == GT_FFA) ? "" : "Blue", delay, uiInfo.teamList[k].teamMembers[i]); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + delay += 500; + } + k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName")); + for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers-1; i++) { + Com_sprintf( buff, sizeof(buff), "addbot \"%s\" %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, (g == GT_FFA) ? "" : "Red", delay, uiInfo.teamList[k].teamMembers[i]); + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + delay += 500; + } + } + if (g >= GT_TEAM ) { + trap_Cmd_ExecuteText( EXEC_APPEND, "wait 5; team Red\n" ); + } +} + +static void UI_Update(const char *name) { + int val = trap_Cvar_VariableValue(name); + + if (Q_stricmp(name, "s_khz") == 0) + { + trap_Cmd_ExecuteText( EXEC_APPEND, "snd_restart\n" ); + return; + } + + if (Q_stricmp(name, "ui_SetName") == 0) { + trap_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) { + trap_Cvar_Set("cl_maxpackets", "30"); + trap_Cvar_Set("cl_packetdup", "1"); + } else if (rate >= 4000) { + trap_Cvar_Set("cl_maxpackets", "15"); + trap_Cvar_Set("cl_packetdup", "2"); // favor less prediction errors when there's packet loss + } else { + trap_Cvar_Set("cl_maxpackets", "15"); + trap_Cvar_Set("cl_packetdup", "1"); // favor lower bandwidth + } + } + else if (Q_stricmp(name, "ui_GetName") == 0) + { + trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString("name")); + } + else if (Q_stricmp(name, "ui_r_colorbits") == 0) + { + switch (val) + { + case 0: + trap_Cvar_SetValue( "ui_r_depthbits", 0 ); + break; + + case 16: + trap_Cvar_SetValue( "ui_r_depthbits", 16 ); + break; + + case 32: + trap_Cvar_SetValue( "ui_r_depthbits", 24 ); + break; + } + } + else if (Q_stricmp(name, "ui_r_lodbias") == 0) + { + switch (val) + { + case 0: + trap_Cvar_SetValue( "ui_r_subdivisions", 4 ); + break; + case 1: + trap_Cvar_SetValue( "ui_r_subdivisions", 12 ); + break; + + case 2: + trap_Cvar_SetValue( "ui_r_subdivisions", 20 ); + break; + } + } + else if (Q_stricmp(name, "ui_r_glCustom") == 0) + { + switch (val) + { + case 0: // high quality + + trap_Cvar_SetValue( "ui_r_fullScreen", 1 ); + trap_Cvar_SetValue( "ui_r_subdivisions", 4 ); + trap_Cvar_SetValue( "ui_r_lodbias", 0 ); + trap_Cvar_SetValue( "ui_r_colorbits", 32 ); + trap_Cvar_SetValue( "ui_r_depthbits", 24 ); + trap_Cvar_SetValue( "ui_r_picmip", 0 ); + trap_Cvar_SetValue( "ui_r_mode", 4 ); + trap_Cvar_SetValue( "ui_r_texturebits", 32 ); + trap_Cvar_SetValue( "ui_r_fastSky", 0 ); + trap_Cvar_SetValue( "ui_r_inGameVideo", 1 ); + //trap_Cvar_SetValue( "ui_cg_shadows", 2 );//stencil + trap_Cvar_Set( "ui_r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + break; + + case 1: // normal + trap_Cvar_SetValue( "ui_r_fullScreen", 1 ); + trap_Cvar_SetValue( "ui_r_subdivisions", 4 ); + trap_Cvar_SetValue( "ui_r_lodbias", 0 ); + trap_Cvar_SetValue( "ui_r_colorbits", 0 ); + trap_Cvar_SetValue( "ui_r_depthbits", 24 ); + trap_Cvar_SetValue( "ui_r_picmip", 1 ); + trap_Cvar_SetValue( "ui_r_mode", 3 ); + trap_Cvar_SetValue( "ui_r_texturebits", 0 ); + trap_Cvar_SetValue( "ui_r_fastSky", 0 ); + trap_Cvar_SetValue( "ui_r_inGameVideo", 1 ); + //trap_Cvar_SetValue( "ui_cg_shadows", 2 ); + trap_Cvar_Set( "ui_r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + break; + + case 2: // fast + + trap_Cvar_SetValue( "ui_r_fullScreen", 1 ); + trap_Cvar_SetValue( "ui_r_subdivisions", 12 ); + trap_Cvar_SetValue( "ui_r_lodbias", 1 ); + trap_Cvar_SetValue( "ui_r_colorbits", 0 ); + trap_Cvar_SetValue( "ui_r_depthbits", 0 ); + trap_Cvar_SetValue( "ui_r_picmip", 2 ); + trap_Cvar_SetValue( "ui_r_mode", 3 ); + trap_Cvar_SetValue( "ui_r_texturebits", 0 ); + trap_Cvar_SetValue( "ui_r_fastSky", 1 ); + trap_Cvar_SetValue( "ui_r_inGameVideo", 0 ); + //trap_Cvar_SetValue( "ui_cg_shadows", 1 ); + trap_Cvar_Set( "ui_r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); + break; + + case 3: // fastest + + trap_Cvar_SetValue( "ui_r_fullScreen", 1 ); + trap_Cvar_SetValue( "ui_r_subdivisions", 20 ); + trap_Cvar_SetValue( "ui_r_lodbias", 2 ); + trap_Cvar_SetValue( "ui_r_colorbits", 16 ); + trap_Cvar_SetValue( "ui_r_depthbits", 16 ); + trap_Cvar_SetValue( "ui_r_mode", 3 ); + trap_Cvar_SetValue( "ui_r_picmip", 3 ); + trap_Cvar_SetValue( "ui_r_texturebits", 16 ); + trap_Cvar_SetValue( "ui_r_fastSky", 1 ); + trap_Cvar_SetValue( "ui_r_inGameVideo", 0 ); + //trap_Cvar_SetValue( "ui_cg_shadows", 0 ); + trap_Cvar_Set( "ui_r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" ); + break; + } + } + else if (Q_stricmp(name, "ui_mousePitch") == 0) + { + if (val == 0) + { + trap_Cvar_SetValue( "m_pitch", 0.022f ); + } + else + { + trap_Cvar_SetValue( "m_pitch", -0.022f ); + } + } + else if (Q_stricmp(name, "ui_mousePitchVeh") == 0) + { + if (val == 0) + { + trap_Cvar_SetValue( "m_pitchVeh", 0.022f ); + } + else + { + trap_Cvar_SetValue( "m_pitchVeh", -0.022f ); + } + } +} + +int gUISelectedMap = 0; + +/* +=============== +UI_DeferMenuScript + +Return true if the menu script should be deferred for later +=============== +*/ +static qboolean UI_DeferMenuScript ( char **args ) +{ + const char* name; + + // Whats the reason for being deferred? + if (!String_Parse( (char**)args, &name)) + { + return qfalse; + } + + // Handle the custom cases + if ( !Q_stricmp ( name, "VideoSetup" ) ) + { + const char* warningMenuName; + qboolean deferred; + + // No warning menu specified + if ( !String_Parse( (char**)args, &warningMenuName) ) + { + return qfalse; + } + + // Defer if the video options were modified + deferred = trap_Cvar_VariableValue ( "ui_r_modified" ) ? qtrue : qfalse; + + if ( deferred ) + { + // Open the warning menu + Menus_OpenByName(warningMenuName); + } + + return deferred; + } + else if ( !Q_stricmp ( name, "RulesBackout" ) ) + { + qboolean deferred; + + deferred = trap_Cvar_VariableValue ( "ui_rules_backout" ) ? qtrue : qfalse ; + + trap_Cvar_Set ( "ui_rules_backout", "0" ); + + return deferred; + } + + return qfalse; +} + +/* +================= +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 ) +{ + trap_Cvar_Set ( "r_mode", UI_Cvar_VariableString ( "ui_r_mode" ) ); + trap_Cvar_Set ( "r_fullscreen", UI_Cvar_VariableString ( "ui_r_fullscreen" ) ); + trap_Cvar_Set ( "r_colorbits", UI_Cvar_VariableString ( "ui_r_colorbits" ) ); + trap_Cvar_Set ( "r_lodbias", UI_Cvar_VariableString ( "ui_r_lodbias" ) ); + trap_Cvar_Set ( "r_picmip", UI_Cvar_VariableString ( "ui_r_picmip" ) ); + trap_Cvar_Set ( "r_texturebits", UI_Cvar_VariableString ( "ui_r_texturebits" ) ); + trap_Cvar_Set ( "r_texturemode", UI_Cvar_VariableString ( "ui_r_texturemode" ) ); + trap_Cvar_Set ( "r_detailtextures", UI_Cvar_VariableString ( "ui_r_detailtextures" ) ); + trap_Cvar_Set ( "r_ext_compress_textures", UI_Cvar_VariableString ( "ui_r_ext_compress_textures" ) ); + trap_Cvar_Set ( "r_depthbits", UI_Cvar_VariableString ( "ui_r_depthbits" ) ); + trap_Cvar_Set ( "r_subdivisions", UI_Cvar_VariableString ( "ui_r_subdivisions" ) ); + trap_Cvar_Set ( "r_fastSky", UI_Cvar_VariableString ( "ui_r_fastSky" ) ); + trap_Cvar_Set ( "r_inGameVideo", UI_Cvar_VariableString ( "ui_r_inGameVideo" ) ); + trap_Cvar_Set ( "r_allowExtensions", UI_Cvar_VariableString ( "ui_r_allowExtensions" ) ); + trap_Cvar_Set ( "cg_shadows", UI_Cvar_VariableString ( "ui_cg_shadows" ) ); + trap_Cvar_Set ( "ui_r_modified", "0" ); + + trap_Cmd_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. + trap_Cvar_Register ( NULL, "ui_r_glCustom", "4", CVAR_ROM|CVAR_INTERNAL|CVAR_ARCHIVE ); + + trap_Cvar_Register ( NULL, "ui_r_mode", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_fullscreen", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_colorbits", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_lodbias", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_picmip", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_texturebits", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_texturemode", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_detailtextures", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_ext_compress_textures","0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_depthbits", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_subdivisions", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_fastSky", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_inGameVideo", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_allowExtensions", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_cg_shadows", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register ( NULL, "ui_r_modified", "0", CVAR_ROM|CVAR_INTERNAL ); + + // Copy over the real video cvars into their temporary counterparts + trap_Cvar_Set ( "ui_r_mode", UI_Cvar_VariableString ( "r_mode" ) ); + trap_Cvar_Set ( "ui_r_colorbits", UI_Cvar_VariableString ( "r_colorbits" ) ); + trap_Cvar_Set ( "ui_r_fullscreen", UI_Cvar_VariableString ( "r_fullscreen" ) ); + trap_Cvar_Set ( "ui_r_lodbias", UI_Cvar_VariableString ( "r_lodbias" ) ); + trap_Cvar_Set ( "ui_r_picmip", UI_Cvar_VariableString ( "r_picmip" ) ); + trap_Cvar_Set ( "ui_r_texturebits", UI_Cvar_VariableString ( "r_texturebits" ) ); + trap_Cvar_Set ( "ui_r_texturemode", UI_Cvar_VariableString ( "r_texturemode" ) ); + trap_Cvar_Set ( "ui_r_detailtextures", UI_Cvar_VariableString ( "r_detailtextures" ) ); + trap_Cvar_Set ( "ui_r_ext_compress_textures", UI_Cvar_VariableString ( "r_ext_compress_textures" ) ); + trap_Cvar_Set ( "ui_r_depthbits", UI_Cvar_VariableString ( "r_depthbits" ) ); + trap_Cvar_Set ( "ui_r_subdivisions", UI_Cvar_VariableString ( "r_subdivisions" ) ); + trap_Cvar_Set ( "ui_r_fastSky", UI_Cvar_VariableString ( "r_fastSky" ) ); + trap_Cvar_Set ( "ui_r_inGameVideo", UI_Cvar_VariableString ( "r_inGameVideo" ) ); + trap_Cvar_Set ( "ui_r_allowExtensions", UI_Cvar_VariableString ( "r_allowExtensions" ) ); + trap_Cvar_Set ( "ui_cg_shadows", UI_Cvar_VariableString ( "cg_shadows" ) ); + trap_Cvar_Set ( "ui_r_modified", "0" ); +} + +// If the game type is siege, hide the addbot button. I would have done a cvar text on that item, +// but it already had one on it. +static void UI_SetBotButton ( void ) +{ + int gameType = trap_Cvar_VariableValue( "g_gametype" ); + int server; + menuDef_t *menu; + itemDef_t *item; + char *name = "addBot"; + + server = trap_Cvar_VariableValue( "sv_running" ); + + // If in siege or a client, don't show add bot button + if ((gameType==GT_SIEGE) || (server==0)) // If it's not siege, don't worry about it + { + menu = Menu_GetFocused(); // Get current menu (either video or ingame video, I would assume) + + if (!menu) + { + return; + } + + item = Menu_FindItemByName(menu, name); + if (item) + { + Menu_ShowItemByName(menu, name, qfalse); + } + } +} + +// Update the model cvar and everything is good. +static void UI_UpdateCharacterCvars ( void ) +{ + char skin[MAX_QPATH]; + char model[MAX_QPATH]; + char head[MAX_QPATH]; + char torso[MAX_QPATH]; + char legs[MAX_QPATH]; + + trap_Cvar_VariableStringBuffer("ui_char_model", model, sizeof(model)); + trap_Cvar_VariableStringBuffer("ui_char_skin_head", head, sizeof(head)); + trap_Cvar_VariableStringBuffer("ui_char_skin_torso", torso, sizeof(torso)); + trap_Cvar_VariableStringBuffer("ui_char_skin_legs", legs, sizeof(legs)); + + Com_sprintf( skin, sizeof( skin ), "%s/%s|%s|%s", + model, + head, + torso, + legs + ); + + trap_Cvar_Set ( "model", skin ); + + trap_Cvar_Set ( "char_color_red", UI_Cvar_VariableString ( "ui_char_color_red" ) ); + trap_Cvar_Set ( "char_color_green", UI_Cvar_VariableString ( "ui_char_color_green" ) ); + trap_Cvar_Set ( "char_color_blue", UI_Cvar_VariableString ( "ui_char_color_blue" ) ); + trap_Cvar_Set ( "ui_selectedModelIndex", "-1"); + +} + +static void UI_GetCharacterCvars ( void ) +{ + char *model; + char *skin; + int i; + + trap_Cvar_Set ( "ui_char_color_red", UI_Cvar_VariableString ( "char_color_red" ) ); + trap_Cvar_Set ( "ui_char_color_green", UI_Cvar_VariableString ( "char_color_green" ) ); + trap_Cvar_Set ( "ui_char_color_blue", UI_Cvar_VariableString ( "char_color_blue" ) ); + + model = UI_Cvar_VariableString ( "model" ); + skin = strrchr(model,'/'); + if (skin && strchr(model,'|')) //we have a multipart custom jedi + { + char skinhead[MAX_QPATH]; + char skintorso[MAX_QPATH]; + char skinlower[MAX_QPATH]; + char *p2; + + *skin=0; + skin++; + //now get the the individual files + + //advance to second + p2 = strchr(skin, '|'); + assert(p2); + *p2=0; + p2++; + strcpy (skinhead, skin); + + + //advance to third + skin = strchr(p2, '|'); + assert(skin); + *skin=0; + skin++; + strcpy (skintorso,p2); + + strcpy (skinlower,skin); + + + + trap_Cvar_Set("ui_char_model", model); + trap_Cvar_Set("ui_char_skin_head", skinhead); + trap_Cvar_Set("ui_char_skin_torso", skintorso); + trap_Cvar_Set("ui_char_skin_legs", skinlower); + + for (i = 0; i < uiInfo.playerSpeciesCount; i++) + { + if ( !stricmp(model, uiInfo.playerSpecies[i].Name) ) + { + uiInfo.playerSpeciesIndex = i; + break; + } + } + } + else + { + model = UI_Cvar_VariableString ( "ui_char_model" ); + for (i = 0; i < uiInfo.playerSpeciesCount; i++) + { + if ( !stricmp(model, uiInfo.playerSpecies[i].Name) ) + { + uiInfo.playerSpeciesIndex = i; + return; //FOUND IT, don't fall through + } + } + //nope, didn't find it. + uiInfo.playerSpeciesIndex = 0;//jic + trap_Cvar_Set("ui_char_model", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].Name); + trap_Cvar_Set("ui_char_skin_head", "head_a1"); + trap_Cvar_Set("ui_char_skin_torso","torso_a1"); + trap_Cvar_Set("ui_char_skin_legs", "lower_a1"); + } +} + +void UI_SetSiegeObjectiveGraphicPos(menuDef_t *menu,const char *itemName,const char *cvarName) +{ + itemDef_t *item; + char cvarBuf[1024]; + const char *holdVal; + char *holdBuf; + + item = Menu_FindItemByName(menu, itemName); + + if (item) + { + // get cvar data + trap_Cvar_VariableStringBuffer(cvarName, cvarBuf, sizeof(cvarBuf)); + + holdBuf = cvarBuf; + if (String_Parse(&holdBuf,&holdVal)) + { + item->window.rectClient.x = atof(holdVal); + if (String_Parse(&holdBuf,&holdVal)) + { + item->window.rectClient.y = atof(holdVal); + if (String_Parse(&holdBuf,&holdVal)) + { + item->window.rectClient.w = atof(holdVal); + if (String_Parse(&holdBuf,&holdVal)) + { + item->window.rectClient.h = atof(holdVal); + + item->window.rect.x = item->window.rectClient.x; + item->window.rect.y = item->window.rectClient.y; + + item->window.rect.w = item->window.rectClient.w; + item->window.rect.h = item->window.rectClient.h; + } + } + } + } + } +} + +void UI_FindCurrentSiegeTeamClass( void ) +{ + menuDef_t *menu; + int myTeam = (int)(trap_Cvar_VariableValue("ui_myteam")); + char *itemname; + itemDef_t *item; + int baseClass; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + if (( myTeam != TEAM_RED ) && ( myTeam != TEAM_BLUE )) + { + return; + } + + // If the player is on a team, + if ( myTeam == TEAM_RED ) + { + itemDef_t *item; + item = (itemDef_t *) Menu_FindItemByName(menu, "onteam1" ); + if (item) + { + Item_RunScript(item, item->action); + } + } + else if ( myTeam == TEAM_BLUE ) + { + itemDef_t *item; + item = (itemDef_t *) Menu_FindItemByName(menu, "onteam2" ); + if (item) + { + Item_RunScript(item, item->action); + } + } + + + baseClass = (int)trap_Cvar_VariableValue("ui_siege_class"); + + // Find correct class button and activate it. + if (baseClass == SPC_INFANTRY) + { + itemname = "class1_button"; + } + else if (baseClass == SPC_HEAVY_WEAPONS) + { + itemname = "class2_button"; + } + else if (baseClass == SPC_DEMOLITIONIST) + { + itemname = "class3_button"; + } + else if (baseClass == SPC_VANGUARD) + { + itemname = "class4_button"; + } + else if (baseClass == SPC_SUPPORT) + { + itemname = "class5_button"; + } + else if (baseClass == SPC_SUPPORT) + { + itemname = "class5_button"; + } + else if (baseClass == SPC_JEDI) + { + itemname = "class6_button"; + } + else + { + return; + } + + item = (itemDef_t *) Menu_FindItemByName(menu, itemname ); + if (item) + { + Item_RunScript(item, item->action); + } + +} + +void UI_UpdateSiegeObjectiveGraphics( void ) +{ + menuDef_t *menu; + int teamI,objI; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + // Hiding a bunch of fields because the opening section of the siege menu was getting too long + Menu_ShowGroup(menu,"class_button",qfalse); + Menu_ShowGroup(menu,"class_count",qfalse); + Menu_ShowGroup(menu,"feeders",qfalse); + Menu_ShowGroup(menu,"classdescription",qfalse); + Menu_ShowGroup(menu,"minidesc",qfalse); + Menu_ShowGroup(menu,"obj_longdesc",qfalse); + Menu_ShowGroup(menu,"objective_pic",qfalse); + Menu_ShowGroup(menu,"stats",qfalse); + Menu_ShowGroup(menu,"forcepowerlevel",qfalse); + + // Get objective icons for each team + for (teamI=1;teamI<3;teamI++) + { + for (objI=1;objI<8;objI++) + { + Menu_SetItemBackground(menu,va("tm%i_icon%i",teamI,objI),va("*team%i_objective%i_mapicon",teamI,objI)); + Menu_SetItemBackground(menu,va("tm%i_l_icon%i",teamI,objI),va("*team%i_objective%i_mapicon",teamI,objI)); + } + } + + // Now get their placement on the map + for (teamI=1;teamI<3;teamI++) + { + for (objI=1;objI<8;objI++) + { + UI_SetSiegeObjectiveGraphicPos(menu,va("tm%i_icon%i",teamI,objI),va("team%i_objective%i_mappos",teamI,objI)); + } + } + +} + +saber_colors_t TranslateSaberColor( const char *name ); + +static void UI_UpdateSaberCvars ( void ) +{ + saber_colors_t colorI; + + trap_Cvar_Set ( "saber1", UI_Cvar_VariableString ( "ui_saber" ) ); + trap_Cvar_Set ( "saber2", UI_Cvar_VariableString ( "ui_saber2" ) ); + + colorI = TranslateSaberColor( UI_Cvar_VariableString ( "ui_saber_color" ) ); + trap_Cvar_Set ( "color1", va("%d",colorI)); + trap_Cvar_Set ( "g_saber_color", UI_Cvar_VariableString ( "ui_saber_color" )); + + colorI = TranslateSaberColor( UI_Cvar_VariableString ( "ui_saber2_color" ) ); + trap_Cvar_Set ( "color2", va("%d",colorI) ); + trap_Cvar_Set ( "g_saber2_color", UI_Cvar_VariableString ( "ui_saber2_color" )); +} + +// More hard coded goodness for the menus. +static void UI_SetSaberBoxesandHilts (void) +{ + menuDef_t *menu; + itemDef_t *item; + qboolean getBig = qfalse; + char sType[MAX_QPATH]; + + menu = Menu_GetFocused(); // Get current menu (either video or ingame video, I would assume) + + if (!menu) + { + return; + } + + trap_Cvar_VariableStringBuffer( "ui_saber_type", sType, sizeof(sType) ); + + if ( Q_stricmp( "dual", sType ) != 0 ) + { +// trap_Cvar_Set("ui_saber", "single_1"); +// trap_Cvar_Set("ui_saber2", "single_1"); + getBig = qtrue; + } + + else if (Q_stricmp( "staff", sType ) != 0 ) + { +// trap_Cvar_Set("ui_saber", "dual_1"); +// trap_Cvar_Set("ui_saber2", "none"); + getBig = qtrue; + } + + if (!getBig) + { + return; + } + + item = (itemDef_t *) Menu_FindItemByName(menu, "box2middle" ); + + if(item) + { + item->window.rect.x = 212; + item->window.rect.y = 126; + item->window.rect.w = 219; + item->window.rect.h = 44; + } + + item = (itemDef_t *) Menu_FindItemByName(menu, "box2bottom" ); + + if(item) + { + item->window.rect.x = 212; + item->window.rect.y = 170; + item->window.rect.w = 219; + item->window.rect.h = 60; + } + + item = (itemDef_t *) Menu_FindItemByName(menu, "box3middle" ); + + if(item) + { + item->window.rect.x = 418; + item->window.rect.y = 126; + item->window.rect.w = 219; + item->window.rect.h = 44; + } + + item = (itemDef_t *) Menu_FindItemByName(menu, "box3bottom" ); + + if(item) + { + item->window.rect.x = 418; + item->window.rect.y = 170; + item->window.rect.w = 219; + item->window.rect.h = 60; + } +} + +//extern qboolean UI_SaberModelForSaber( const char *saberName, char *saberModel ); +extern qboolean UI_SaberSkinForSaber( const char *saberName, char *saberSkin ); +#include "../namespace_begin.h" +extern qboolean ItemParse_asset_model_go( itemDef_t *item, const char *name,int *runTimeLength ); +extern qboolean ItemParse_model_g2skin_go( itemDef_t *item, const char *skinName ); +#include "../namespace_end.h" + +static void UI_UpdateSaberType( void ) +{ + char sType[MAX_QPATH]; + trap_Cvar_VariableStringBuffer( "ui_saber_type", sType, sizeof(sType) ); + + if ( Q_stricmp( "single", sType ) == 0 || + Q_stricmp( "staff", sType ) == 0 ) + { + trap_Cvar_Set( "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]; + char *itemName; + char *saberCvarName; + int animRunLength; + + menu = Menu_GetFocused(); // Get current menu (either video or ingame video, I would assume) + + if (!menu) + { + return; + } + + if ( secondSaber ) + { + itemName = "saber2"; + saberCvarName = "ui_saber2"; + } + else + { + itemName = "saber"; + saberCvarName = "ui_saber"; + } + + item = (itemDef_t *) Menu_FindItemByName(menu, itemName ); + + if(!item) + { + Com_Error( ERR_FATAL, "UI_UpdateSaberHilt: Could not find item (%s) in menu (%s)", itemName, menu->window.name); + } + + trap_Cvar_VariableStringBuffer( saberCvarName, model, sizeof(model) ); + + item->text = model; + //read this from the sabers.cfg + if ( UI_SaberModelForSaber( model, modelPath ) ) + {//successfully found a model + ItemParse_asset_model_go( item, modelPath, &animRunLength );//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 ) +{ +} + +extern char * SaberColorToString(saber_colors_t color); + +static void UI_GetSaberCvars ( void ) +{ +// trap_Cvar_Set ( "ui_saber_type", UI_Cvar_VariableString ( "g_saber_type" ) ); + trap_Cvar_Set ( "ui_saber", UI_Cvar_VariableString ( "saber1" ) ); + trap_Cvar_Set ( "ui_saber2", UI_Cvar_VariableString ( "saber2" )); + + trap_Cvar_Set("g_saber_color", SaberColorToString(trap_Cvar_VariableValue("color1"))); + trap_Cvar_Set("g_saber2_color", SaberColorToString(trap_Cvar_VariableValue("color2"))); + + trap_Cvar_Set ( "ui_saber_color", UI_Cvar_VariableString ( "g_saber_color" ) ); + trap_Cvar_Set ( "ui_saber2_color", UI_Cvar_VariableString ( "g_saber2_color" ) ); + + +} + + +//extern qboolean ItemParse_model_g2skin_go( itemDef_t *item, const char *skinName ); +#include "../namespace_begin.h" +extern qboolean ItemParse_model_g2anim_go( itemDef_t *item, const char *animName ); +#include "../namespace_end.h" +//extern qboolean ItemParse_asset_model_go( itemDef_t *item, const char *name ); + +void UI_UpdateCharacterSkin( void ) +{ + menuDef_t *menu; + itemDef_t *item; + char skin[MAX_QPATH]; + char model[MAX_QPATH]; + char head[MAX_QPATH]; + char torso[MAX_QPATH]; + char legs[MAX_QPATH]; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + item = (itemDef_t *) Menu_FindItemByName(menu, "character"); + + if (!item) + { + Com_Error( ERR_FATAL, "UI_UpdateCharacterSkin: Could not find item (character) in menu (%s)", menu->window.name); + } + + trap_Cvar_VariableStringBuffer("ui_char_model", model, sizeof(model)); + trap_Cvar_VariableStringBuffer("ui_char_skin_head", head, sizeof(head)); + trap_Cvar_VariableStringBuffer("ui_char_skin_torso", torso, sizeof(torso)); + trap_Cvar_VariableStringBuffer("ui_char_skin_legs", legs, sizeof(legs)); + + Com_sprintf( skin, sizeof( skin ), "models/players/%s/|%s|%s|%s", + model, + head, + torso, + legs + ); + + ItemParse_model_g2skin_go( item, skin ); +} + +static void UI_ResetCharacterListBoxes( void ) +{ + + itemDef_t *item; + menuDef_t *menu; + listBoxDef_t *listPtr; + + menu = Menu_GetFocused(); + + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "headlistbox"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if( listPtr ) + { + listPtr->cursorPos = 0; + } + item->cursorPos = 0; + } + + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "torsolistbox"); + if (item) + { + listPtr = (listBoxDef_t*)item->typeData; + if( listPtr ) + { + listPtr->cursorPos = 0; + } + item->cursorPos = 0; + } + + item = (itemDef_t *) 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; + } + } +} + +#define MAX_SABER_HILTS 64 + +char *saberSingleHiltInfo [MAX_SABER_HILTS]; +char *saberStaffHiltInfo [MAX_SABER_HILTS]; + +qboolean UI_SaberProperNameForSaber( const char *saberName, char *saberProperName ); +void UI_SaberGetHiltInfo( char *singleHilts[MAX_SABER_HILTS],char *staffHilts[MAX_SABER_HILTS] ); + + +static void UI_UpdateCharacter( qboolean changedModel ) +{ + menuDef_t *menu; + itemDef_t *item; + char modelPath[MAX_QPATH]; + int animRunLength; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + item = (itemDef_t *) 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", UI_Cvar_VariableString ( "ui_char_model" ) ); + ItemParse_asset_model_go( item, modelPath, &animRunLength ); + + 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(); +} + +static void UI_RunMenuScript(char **args) +{ + const char *name, *name2; + char buff[1024]; + + if (String_Parse(args, &name)) + { + if (Q_stricmp(name, "StartServer") == 0) + { + int i, added = 0; + float skill; + int warmupTime = 0; + int doWarmup = 0; + + trap_Cvar_Set("cg_thirdPerson", "0"); + trap_Cvar_Set("cg_cameraOrbit", "0"); + // for Solo games I set this to 1 in the menu and don't want it stomped here, + // this cvar seems to be reset to 0 in all the proper places so... -dmv + // trap_Cvar_Set("ui_singlePlayerActive", "0"); + + // if a solo game is started, automatically turn dedicated off here (don't want to do it in the menu, might get annoying) + if( trap_Cvar_VariableValue( "ui_singlePlayerActive" ) ) + { + trap_Cvar_Set( "dedicated", "0" ); + } + else + { + trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) ); + } + trap_Cvar_SetValue( "g_gametype", Com_Clamp( 0, 8, uiInfo.gameTypes[ui_netGameType.integer].gtEnum ) ); + trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName")); + trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName")); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ) ); + skill = trap_Cvar_VariableValue( "g_spSkill" ); + + //Cap the warmup values in case the user tries a dumb setting. + warmupTime = trap_Cvar_VariableValue( "g_warmup" ); + doWarmup = trap_Cvar_VariableValue( "g_doWarmup" ); + + if (doWarmup && warmupTime < 1) + { + trap_Cvar_Set("g_doWarmup", "0"); + } + if (warmupTime < 5) + { + trap_Cvar_Set("g_warmup", "5"); + } + if (warmupTime > 120) + { + trap_Cvar_Set("g_warmup", "120"); + } + + if (trap_Cvar_VariableValue( "g_gametype" ) == GT_DUEL || + trap_Cvar_VariableValue( "g_gametype" ) == GT_POWERDUEL) + { //always set fraglimit 1 when starting a duel game + trap_Cvar_Set("fraglimit", "1"); + } + + for (i = 0; i < PLAYERS_PER_TEAM; i++) + { + int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1)); + int maxcl = trap_Cvar_VariableValue( "sv_maxClients" ); + + if (bot > 1) + { + int numval = i+1; + + numval *= 2; + + numval -= 1; + + if (numval <= maxcl) + { + if (ui_actualNetGameType.integer >= GT_TEAM) { + Com_sprintf( buff, sizeof(buff), "addbot \"%s\" %f %s\n", UI_GetBotNameByNumber(bot-2), skill, "Blue"); + } else { + Com_sprintf( buff, sizeof(buff), "addbot \"%s\" %f \n", UI_GetBotNameByNumber(bot-2), skill); + } + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + added++; + } + } + bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1)); + if (bot > 1) { + int numval = i+1; + + numval *= 2; + + if (numval <= maxcl) + { + if (ui_actualNetGameType.integer >= GT_TEAM) { + Com_sprintf( buff, sizeof(buff), "addbot \"%s\" %f %s\n", UI_GetBotNameByNumber(bot-2), skill, "Red"); + } else { + Com_sprintf( buff, sizeof(buff), "addbot \"%s\" %f \n", UI_GetBotNameByNumber(bot-2), skill); + } + trap_Cmd_ExecuteText( EXEC_APPEND, buff ); + added++; + } + } + if (added >= maxcl) + { //this means the client filled up all their slots in the UI with bots. So stretch out an extra slot for them, and then stop adding bots. + trap_Cvar_Set("sv_maxClients", va("%i", added+1)); + break; + } + } + } else if (Q_stricmp(name, "updateSPMenu") == 0) { + UI_SetCapFragLimits(qtrue); + UI_MapCountByGameType(qtrue); + ui_mapIndex.integer = UI_GetIndexFromSelection(ui_currentMap.integer); + trap_Cvar_Set("ui_mapIndex", va("%d", ui_mapIndex.integer)); + Menu_SetFeederSelection(NULL, FEEDER_MAPS, ui_mapIndex.integer, "skirmish"); + UI_GameType_HandleKey(0, 0, A_MOUSE1, qfalse); + UI_GameType_HandleKey(0, 0, A_MOUSE2, qfalse); + } else if (Q_stricmp(name, "resetDefaults") == 0) { + trap_Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n"); + trap_Cmd_ExecuteText( EXEC_APPEND, "exec mpdefault.cfg\n"); + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" ); + trap_Cvar_Set("com_introPlayed", "1" ); +#ifdef USE_CD_KEY + } else if (Q_stricmp(name, "getCDKey") == 0) { + char out[17]; + trap_GetCDKey(buff, 17); + trap_Cvar_Set("cdkey1", ""); + trap_Cvar_Set("cdkey2", ""); + trap_Cvar_Set("cdkey3", ""); + trap_Cvar_Set("cdkey4", ""); + if (strlen(buff) == CDKEY_LEN) { + Q_strncpyz(out, buff, 5); + trap_Cvar_Set("cdkey1", out); + Q_strncpyz(out, buff + 4, 5); + trap_Cvar_Set("cdkey2", out); + Q_strncpyz(out, buff + 8, 5); + trap_Cvar_Set("cdkey3", out); + Q_strncpyz(out, buff + 12, 5); + trap_Cvar_Set("cdkey4", out); + } + + } else if (Q_stricmp(name, "verifyCDKey") == 0) { + buff[0] = '\0'; + Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkey1")); + Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkey2")); + Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkey3")); + Q_strcat(buff, 1024, UI_Cvar_VariableString("cdkey4")); + trap_Cvar_Set("cdkey", buff); + if (trap_VerifyCDKey(buff, UI_Cvar_VariableString("cdkeychecksum"))) { + trap_Cvar_Set("ui_cdkeyvalid", "CD Key Appears to be valid."); + trap_SetCDKey(buff); + } else { + trap_Cvar_Set("ui_cdkeyvalid", "CD Key does not appear to be valid."); + } +#endif // USE_CD_KEY + } else if (Q_stricmp(name, "loadArenas") == 0) { + UI_LoadArenas(); + UI_MapCountByGameType(qfalse); + Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, gUISelectedMap, "createserver"); + uiForceRank = trap_Cvar_VariableValue("g_maxForceRank"); + } 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) { + trap_Cvar_Set("com_errorMessage", ""); + } else if (Q_stricmp(name, "loadGameInfo") == 0) { + UI_ParseGameInfo("ui/jamp/gameinfo.txt"); + UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); + } else if (Q_stricmp(name, "resetScores") == 0) { + UI_ClearScores(); + } else if (Q_stricmp(name, "RefreshServers") == 0) { + UI_StartServerRefresh(qtrue); + UI_BuildServerDisplayList(qtrue); + } else if (Q_stricmp(name, "RefreshFilter") == 0) { + UI_StartServerRefresh(qfalse); + UI_BuildServerDisplayList(qtrue); + } else if (Q_stricmp(name, "RunSPDemo") == 0) { + if (uiInfo.demoAvailable) { + trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s_%i\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum)); + } + } else if (Q_stricmp(name, "LoadDemos") == 0) { + UI_LoadDemos(); + } else if (Q_stricmp(name, "LoadMovies") == 0) { + UI_LoadMovies(); + } else if (Q_stricmp(name, "LoadMods") == 0) { + UI_LoadMods(); + } else if (Q_stricmp(name, "playMovie") == 0) { + if (uiInfo.previewMovie >= 0) { + trap_CIN_StopCinematic(uiInfo.previewMovie); + } + trap_Cmd_ExecuteText( EXEC_APPEND, va("cinematic %s.roq 2\n", uiInfo.movieList[uiInfo.movieIndex])); + } else if (Q_stricmp(name, "RunMod") == 0) { + trap_Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName); + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); + } else if (Q_stricmp(name, "RunDemo") == 0) { + trap_Cmd_ExecuteText( EXEC_APPEND, va("demo \"%s\"\n", uiInfo.demoList[uiInfo.demoIndex])); + } else if (Q_stricmp(name, "Quake3") == 0) { + trap_Cvar_Set( "fs_game", ""); + trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" ); + } else if (Q_stricmp(name, "closeJoin") == 0) { + if (uiInfo.serverStatus.refreshActive) { + UI_StopServerRefresh(); + uiInfo.serverStatus.nextDisplayRefresh = 0; + uiInfo.nextServerStatusRefresh = 0; + uiInfo.nextFindPlayerRefresh = 0; + UI_BuildServerDisplayList(qtrue); + } else { + Menus_CloseByName("joinserver"); + Menus_OpenByName("main"); + } + } else if (Q_stricmp(name, "StopRefresh") == 0) { + UI_StopServerRefresh(); + uiInfo.serverStatus.nextDisplayRefresh = 0; + uiInfo.nextServerStatusRefresh = 0; + uiInfo.nextFindPlayerRefresh = 0; + } else if (Q_stricmp(name, "UpdateFilter") == 0) { + if (ui_netSource.integer == AS_LOCAL) { + UI_StartServerRefresh(qtrue); + } + UI_BuildServerDisplayList(qtrue); + UI_FeederSelection(FEEDER_SERVERS, 0, NULL ); + + } else if (Q_stricmp(name, "ServerStatus") == 0) { + trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], uiInfo.serverStatusAddress, sizeof(uiInfo.serverStatusAddress)); + UI_BuildServerStatus(qtrue); + } else if (Q_stricmp(name, "FoundPlayerServerStatus") == 0) { + Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress)); + UI_BuildServerStatus(qtrue); + Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL); + } else if (Q_stricmp(name, "FindPlayer") == 0) { + UI_BuildFindPlayerList(qtrue); + // clear the displayed server status info + uiInfo.serverStatusInfo.numLines = 0; + Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL); + } + else if (Q_stricmp(name, "checkservername") == 0) + { + UI_CheckServerName(); + + } + else if (Q_stricmp(name, "checkpassword") == 0) + { + if( UI_CheckPassword() ) + { + UI_JoinServer(); + } + + } + else if (Q_stricmp(name, "JoinServer") == 0) + { + UI_JoinServer(); + } + else if (Q_stricmp(name, "FoundPlayerJoinServer") == 0) { + trap_Cvar_Set("ui_singlePlayerActive", "0"); + if (uiInfo.currentFoundPlayerServer >= 0 && uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers) { + trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer] ) ); + } + } else if (Q_stricmp(name, "Quit") == 0) { + trap_Cvar_Set("ui_singlePlayerActive", "0"); + trap_Cmd_ExecuteText( EXEC_NOW, "quit"); + } else if (Q_stricmp(name, "Controls") == 0) { + trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName("setup_menu2"); + } + else if (Q_stricmp(name, "Leave") == 0) + { + trap_Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName("main"); + } + else if (Q_stricmp(name, "getvideosetup") == 0) + { + UI_GetVideoSetup ( ); + } + else if (Q_stricmp(name, "getsaberhiltinfo") == 0) + { + UI_SaberGetHiltInfo(saberSingleHiltInfo,saberStaffHiltInfo); + } + // On the solo game creation screen, we can't see siege maps + else if (Q_stricmp(name, "checkforsiege") == 0) + { + if (uiInfo.gameTypes[ui_netGameType.integer].gtEnum == GT_SIEGE) + { + // fake out the handler to advance to the next game type + UI_NetGameType_HandleKey(0, NULL, A_MOUSE1); + } + } + else if (Q_stricmp(name, "updatevideosetup") == 0) + { + UI_UpdateVideoSetup ( ); + } + else if (Q_stricmp(name, "ServerSort") == 0) + { + int sortColumn; + if (Int_Parse(args, &sortColumn)) { + // if same column we're already sorting on then flip the direction + if (sortColumn == uiInfo.serverStatus.sortKey) { + uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir; + } + // make sure we sort again + UI_ServersSort(sortColumn, qtrue); + } + } else if (Q_stricmp(name, "nextSkirmish") == 0) { + UI_StartSkirmish(qtrue); + } else if (Q_stricmp(name, "SkirmishStart") == 0) { + UI_StartSkirmish(qfalse); + } else if (Q_stricmp(name, "closeingame") == 0) { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + } else if (Q_stricmp(name, "voteMap") == 0) { + if (ui_currentNetMap.integer >=0 && ui_currentNetMap.integer < uiInfo.mapCount) { + trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote map %s\n",uiInfo.mapList[ui_currentNetMap.integer].mapLoadName) ); + } + } else if (Q_stricmp(name, "voteKick") == 0) { + if (uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount) { + //trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote kick \"%s\"\n",uiInfo.playerNames[uiInfo.playerIndex]) ); + trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote clientkick \"%i\"\n",uiInfo.playerIndexes[uiInfo.playerIndex]) ); + } + } else if (Q_stricmp(name, "voteGame") == 0) { + if (ui_netGameType.integer >= 0 && ui_netGameType.integer < uiInfo.numGameTypes) { + trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote g_gametype %i\n",uiInfo.gameTypes[ui_netGameType.integer].gtEnum) ); + } + } else if (Q_stricmp(name, "voteLeader") == 0) { + if (uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount) { + trap_Cmd_ExecuteText( EXEC_APPEND, va("callteamvote leader \"%s\"\n",uiInfo.teamNames[uiInfo.teamIndex]) ); + } + } else if (Q_stricmp(name, "addBot") == 0) { + if (trap_Cvar_VariableValue("g_gametype") >= GT_TEAM) { + trap_Cmd_ExecuteText( EXEC_APPEND, va("addbot \"%s\" %i %s\n", UI_GetBotNameByNumber(uiInfo.botIndex), uiInfo.skillIndex+1, (uiInfo.redBlue == 0) ? "Red" : "Blue") ); + } else { + trap_Cmd_ExecuteText( EXEC_APPEND, va("addbot \"%s\" %i %s\n", UI_GetBotNameByNumber(uiInfo.botIndex), uiInfo.skillIndex+1, (uiInfo.redBlue == 0) ? "Red" : "Blue") ); + } + } else if (Q_stricmp(name, "addFavorite") == 0) + { + if (ui_netSource.integer != AS_FAVORITES) + { + char name[MAX_NAME_LENGTH]; + char addr[MAX_NAME_LENGTH]; + int res; + + trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS); + name[0] = addr[0] = '\0'; + Q_strncpyz(name, Info_ValueForKey(buff, "hostname"), MAX_NAME_LENGTH); + Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH); + if (strlen(name) > 0 && strlen(addr) > 0) + { + res = trap_LAN_AddServer(AS_FAVORITES, name, addr); + if (res == 0) + { + // server already in the list + Com_Printf("Favorite already in list\n"); + } + else if (res == -1) + { + // list full + Com_Printf("Favorite list full\n"); + } + else + { + // successfully added + Com_Printf("Added favorite server %s\n", addr); + + +// trap_SP_GetStringTextString((char *)va("%s_GETTINGINFOFORSERVERS",uiInfo.uiDC.Assets.stringedFile), holdSPString, sizeof(holdSPString)); +// Text_Paint(rect->x, rect->y, scale, newColor, va((char *) holdSPString, trap_LAN_GetServerCount(ui_netSource.integer)), 0, 0, textStyle); + + } + } + } + } + else if (Q_stricmp(name, "deleteFavorite") == 0) + { + if (ui_netSource.integer == AS_FAVORITES) + { + char addr[MAX_NAME_LENGTH]; + trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS); + addr[0] = '\0'; + Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH); + if (strlen(addr) > 0) + { + trap_LAN_RemoveServer(AS_FAVORITES, addr); + } + } + } + else if (Q_stricmp(name, "createFavorite") == 0) + { + // if (ui_netSource.integer == AS_FAVORITES) + //rww - don't know why this check was here.. why would you want to only add new favorites when the filter was favorites? + { + char name[MAX_NAME_LENGTH]; + char addr[MAX_NAME_LENGTH]; + int res; + + name[0] = addr[0] = '\0'; + Q_strncpyz(name, UI_Cvar_VariableString("ui_favoriteName"), MAX_NAME_LENGTH); + Q_strncpyz(addr, UI_Cvar_VariableString("ui_favoriteAddress"), MAX_NAME_LENGTH); + if (/*strlen(name) > 0 &&*/ strlen(addr) > 0) { + res = trap_LAN_AddServer(AS_FAVORITES, name, addr); + if (res == 0) { + // server already in the list + Com_Printf("Favorite already in list\n"); + } + else if (res == -1) { + // list full + Com_Printf("Favorite list full\n"); + } + else { + // successfully added + Com_Printf("Added favorite server %s\n", addr); + } + } + } + } else if (Q_stricmp(name, "orders") == 0) { + const char *orders; + if (String_Parse(args, &orders)) { + int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); + if (selectedPlayer < uiInfo.myTeamCount) { + strcpy(buff, orders); + trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) ); + trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); + } else { + int i; + for (i = 0; i < uiInfo.myTeamCount; i++) { + if (Q_stricmp(UI_Cvar_VariableString("name"), uiInfo.teamNames[i]) == 0) { + continue; + } + strcpy(buff, orders); + trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamNames[i]) ); + trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); + } + } + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + } + } else if (Q_stricmp(name, "voiceOrdersTeam") == 0) { + const char *orders; + if (String_Parse(args, &orders)) { + int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); + if (selectedPlayer == uiInfo.myTeamCount) { + trap_Cmd_ExecuteText( EXEC_APPEND, orders ); + trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); + } + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + } + } else if (Q_stricmp(name, "voiceOrders") == 0) { + const char *orders; + if (String_Parse(args, &orders)) { + int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer"); + + if (selectedPlayer == uiInfo.myTeamCount) + { + selectedPlayer = -1; + strcpy(buff, orders); + trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, selectedPlayer) ); + } + else + { + strcpy(buff, orders); + trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) ); + } + trap_Cmd_ExecuteText( EXEC_APPEND, "\n" ); + + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + } + } + else if (Q_stricmp(name, "setForce") == 0) + { + const char *teamArg; + + if (String_Parse(args, &teamArg)) + { + if ( Q_stricmp( "none", teamArg ) == 0 ) + { + UI_UpdateClientForcePowers(NULL); + } + else if ( Q_stricmp( "same", teamArg ) == 0 ) + {//stay on current team + int myTeam = (int)(trap_Cvar_VariableValue("ui_myteam")); + if ( myTeam != TEAM_SPECTATOR ) + { + UI_UpdateClientForcePowers(UI_TeamName(myTeam));//will cause him to respawn, if it's been 5 seconds since last one + } + else + { + UI_UpdateClientForcePowers(NULL);//just update powers + } + } + else + { + UI_UpdateClientForcePowers(teamArg); + } + } + else + { + UI_UpdateClientForcePowers(NULL); + } + } + else if (Q_stricmp(name, "setsiegeclassandteam") == 0) + { + int team = (int)trap_Cvar_VariableValue("ui_holdteam"); + int oldteam = (int)trap_Cvar_VariableValue("ui_startsiegeteam"); + qboolean goTeam = qtrue; + char newclassString[512]; + char startclassString[512]; + + trap_Cvar_VariableStringBuffer( "ui_mySiegeClass", newclassString, sizeof(newclassString) ); + trap_Cvar_VariableStringBuffer( "ui_startsiegeclass", startclassString, sizeof(startclassString) ); + + // Was just a spectator - is still just a spectator + if ((oldteam == team) && (oldteam == 3)) + { + goTeam = qfalse; + } + // If new team and class match old team and class, just return to the game. + else if ((oldteam == team)) + { // Classes match? + if (g_UIGloballySelectedSiegeClass != -1) + { + if (!strcmp(startclassString,bgSiegeClasses[g_UIGloballySelectedSiegeClass].name)) + { + goTeam = qfalse; + } + } + } + + if (goTeam) + { + if (team == 1) // Team red + { + trap_Cvar_Set("ui_team", va("%d", team)); + } + else if (team == 2) // Team blue + { + trap_Cvar_Set("ui_team", va("%d", team)); + } + else if (team == 3) // Team spectator + { + trap_Cvar_Set("ui_team", va("%d", team)); + } + + if (g_UIGloballySelectedSiegeClass != -1) + { + trap_Cmd_ExecuteText( EXEC_APPEND, va("siegeclass \"%s\"\n", bgSiegeClasses[g_UIGloballySelectedSiegeClass].name) ); + } + } + } + else if (Q_stricmp(name, "setBotButton") == 0) + { + UI_SetBotButton(); + } + else if (Q_stricmp(name, "saveTemplate") == 0) { + UI_SaveForceTemplate(); + } else if (Q_stricmp(name, "refreshForce") == 0) { + UI_UpdateForcePowers(); + } else if (Q_stricmp(name, "glCustom") == 0) { + trap_Cvar_Set("ui_r_glCustom", "4"); + } + else if (Q_stricmp(name, "setMovesListDefault") == 0) + { + uiInfo.movesTitleIndex = 2; + } + else if (Q_stricmp(name, "resetMovesList") == 0) + { + menuDef_t *menu; + menu = Menus_FindByName("rulesMenu_moves"); + //update saber models + if (menu) + { + itemDef_t *item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + UI_SaberAttachToChar( item ); + } + } + + trap_Cvar_Set( "ui_move_desc", " " ); + } + else if (Q_stricmp(name, "resetcharacterlistboxes") == 0) + { + UI_ResetCharacterListBoxes(); + } + else if (Q_stricmp(name, "setMoveCharacter") == 0) + { + itemDef_t *item; + menuDef_t *menu; + modelDef_t *modelPtr; + int animRunLength; + + UI_GetCharacterCvars(); + + uiInfo.movesTitleIndex = 0; + + menu = Menus_FindByName("rulesMenu_moves"); + + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + char modelPath[MAX_QPATH]; + + uiInfo.movesBaseAnim = datapadMoveTitleBaseAnims[uiInfo.movesTitleIndex]; + ItemParse_model_g2anim_go( item, uiInfo.movesBaseAnim ); + uiInfo.moveAnimTime = 0 ; + + Com_sprintf( modelPath, sizeof( modelPath ), "models/players/%s/model.glm", UI_Cvar_VariableString ( "ui_char_model" ) ); + ItemParse_asset_model_go( item, modelPath, &animRunLength); + + UI_UpdateCharacterSkin(); + UI_SaberAttachToChar( item ); + } + } + } + } + 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, "updatecharcvars") == 0 + || (Q_stricmp(name, "updatecharmodel") == 0) ) + { + UI_UpdateCharacterCvars(); + } + else if (Q_stricmp(name, "getcharcvars") == 0) + { + UI_GetCharacterCvars(); + } + else if (Q_stricmp(name, "char_skin") == 0) + { + UI_UpdateCharacterSkin(); + } + else if (Q_stricmp(name, "setui_dualforcepower") == 0) + { + int forcePowerDisable = trap_Cvar_VariableValue("g_forcePowerDisable"); + int i, forceBitFlag=0; + + // Turn off all powers but a few + for (i=0;i1) + { + trap_Cvar_Set(va("ui_blueteam%i",i ), "1"); + } + + redValue = trap_Cvar_VariableValue(va("ui_redteam%i",i )); + if (redValue>1) + { + trap_Cvar_Set(va("ui_redteam%i",i ), "1"); + } + + } + } + } + else if (Q_stricmp(name, "clearmouseover") == 0) + { + itemDef_t *item; + menuDef_t *menu = Menu_GetFocused(); + + if (menu) + { + int count,j; + const char *itemName; + String_Parse(args, &itemName); + + count = Menu_ItemsMatchingGroup(menu, itemName); + + for (j = 0; j < count; j++) + { + item = Menu_GetMatchingItemByNumber( menu, j, itemName); + if (item != NULL) + { + item->window.flags &= ~WINDOW_MOUSEOVER; + } + } + } + + } + else if (Q_stricmp(name, "updateForceStatus") == 0) + { + UpdateForceStatus(); + } + else if (Q_stricmp(name, "update") == 0) + { + if (String_Parse(args, &name2)) + { + UI_Update(name2); + } + } + else if (Q_stricmp(name, "setBotButtons") == 0) + { + UpdateBotButtons(); + } + else if (Q_stricmp(name, "getsabercvars") == 0) + { + UI_GetSaberCvars(); + } + else if (Q_stricmp(name, "setsaberboxesandhilts") == 0) + { + UI_SetSaberBoxesandHilts(); + } + 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, "setscreensaberhilt") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "hiltbut"); + if (item) + { + if (saberSingleHiltInfo[item->cursorPos]) + { + trap_Cvar_Set( "ui_saber", saberSingleHiltInfo[item->cursorPos] ); + } + } + } + } + else if (Q_stricmp(name, "setscreensaberhilt1") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "hiltbut1"); + if (item) + { + if (saberSingleHiltInfo[item->cursorPos]) + { + trap_Cvar_Set( "ui_saber", saberSingleHiltInfo[item->cursorPos] ); + } + } + } + } + else if (Q_stricmp(name, "setscreensaberhilt2") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "hiltbut2"); + if (item) + { + if (saberSingleHiltInfo[item->cursorPos]) + { + trap_Cvar_Set( "ui_saber2", saberSingleHiltInfo[item->cursorPos] ); + } + } + } + } + else if (Q_stricmp(name, "setscreensaberstaff") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "hiltbut_staves"); + if (item) + { + if (saberSingleHiltInfo[item->cursorPos]) + { + trap_Cvar_Set( "ui_saber", saberStaffHiltInfo[item->cursorPos] ); + } + } + } + } + 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, "updatesabercvars") == 0) + { + UI_UpdateSaberCvars(); + } + else if (Q_stricmp(name, "updatesiegeobjgraphics") == 0) + { + int team = (int)trap_Cvar_VariableValue("ui_team"); + trap_Cvar_Set("ui_holdteam", va("%d", team)); + + UI_UpdateSiegeObjectiveGraphics(); + } + else if (Q_stricmp(name, "setsiegeobjbuttons") == 0) + { + const char *itemArg; + const char *cvarLitArg; + const char *cvarNormalArg; + char string[512]; + char string2[512]; + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + // Set the new item to the background + if (String_Parse(args, &itemArg)) + { + + // Set the old button to it's original background + trap_Cvar_VariableStringBuffer( "currentObjMapIconItem", string, sizeof(string) ); + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, string); + if (item) + { + // A cvar holding the name of a cvar - how crazy is that? + trap_Cvar_VariableStringBuffer( "currentObjMapIconBackground", string, sizeof(string) ); + trap_Cvar_VariableStringBuffer( string, string2, sizeof(string2) ); + Menu_SetItemBackground(menu, item->window.name, string2); + + // Re-enable this button + Menu_ItemDisable(menu,(char *) item->window.name, qfalse); + } + + // Set the new item to the given background + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, itemArg); + if (item) + { // store item name + trap_Cvar_Set("currentObjMapIconItem", item->window.name); + if (String_Parse(args, &cvarNormalArg)) + { // Store normal background + trap_Cvar_Set("currentObjMapIconBackground", cvarNormalArg); + // Get higlight background + if (String_Parse(args, &cvarLitArg)) + { // set hightlight background + trap_Cvar_VariableStringBuffer( cvarLitArg, string, sizeof(string) ); + Menu_SetItemBackground(menu, item->window.name, string); + // Disable button + Menu_ItemDisable(menu,(char *) item->window.name, qtrue); + } + } + } + } + } + + + } + else if (Q_stricmp(name, "updatesiegeclasscnt") == 0) + { + const char *teamArg; + + if (String_Parse(args, &teamArg)) + { + UI_SiegeClassCnt(atoi(teamArg)); + } + } + else if (Q_stricmp(name, "updatesiegecvars") == 0) + { + int team,baseClass; + + team = (int)trap_Cvar_VariableValue("ui_holdteam"); + baseClass = (int)trap_Cvar_VariableValue("ui_siege_class"); + + UI_UpdateCvarsForClass(team, baseClass, 0); + + } + // Save current team and class + else if (Q_stricmp(name, "setteamclassicons") == 0) + { + int team = (int)trap_Cvar_VariableValue("ui_holdteam"); + char classString[512]; + + trap_Cvar_VariableStringBuffer( "ui_mySiegeClass", classString, sizeof(classString) ); + + trap_Cvar_Set("ui_startsiegeteam", va("%d", team)); + trap_Cvar_Set( "ui_startsiegeclass", classString); + + // If player is already on a team, set up icons to show it. + UI_FindCurrentSiegeTeamClass(); + + } + else if (Q_stricmp(name, "updatesiegeweapondesc") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "base_class_weapons_feed"); + if (item) + { + char info[MAX_INFO_VALUE]; + trap_Cvar_VariableStringBuffer( va("ui_class_weapondesc%i", item->cursorPos), info, sizeof(info) ); + trap_Cvar_Set( "ui_itemforceinvdesc", info ); + } + } + } + else if (Q_stricmp(name, "updatesiegeinventorydesc") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "base_class_inventory_feed"); + if (item) + { + char info[MAX_INFO_VALUE]; + trap_Cvar_VariableStringBuffer( va("ui_class_itemdesc%i", item->cursorPos), info, sizeof(info) ); + trap_Cvar_Set( "ui_itemforceinvdesc", info ); + } + } + } + else if (Q_stricmp(name, "updatesiegeforcedesc") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "base_class_force_feed"); + if (item) + { + int i; + char info[MAX_STRING_CHARS]; + + trap_Cvar_VariableStringBuffer( va("ui_class_power%i", item->cursorPos), info, sizeof(info) ); + + //count them up + for (i=0;i< NUM_FORCE_POWERS;i++) + { + if (!strcmp(HolocronIcons[i],info)) + { + trap_Cvar_Set( "ui_itemforceinvdesc", forcepowerDesc[i] ); + } + } + } + } + } + else if (Q_stricmp(name, "resetitemdescription") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "itemdescription"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if (listPtr) + { + listPtr->startPos = 0; + listPtr->cursorPos = 0; + } + item->cursorPos = 0; + } + } + } + else if (Q_stricmp(name, "resetsiegelistboxes") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "description"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if (listPtr) + { + listPtr->startPos = 0; + } + item->cursorPos = 0; + } + } + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "base_class_weapons_feed"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if (listPtr) + { + listPtr->startPos = 0; + } + item->cursorPos = 0; + } + + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "base_class_inventory_feed"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if (listPtr) + { + listPtr->startPos = 0; + } + item->cursorPos = 0; + } + + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "base_class_force_feed"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if (listPtr) + { + listPtr->startPos = 0; + } + item->cursorPos = 0; + } + } + } + else if (Q_stricmp(name, "updatesiegestatusicons") == 0) + { + UI_UpdateSiegeStatusIcons(); + } + else if (Q_stricmp(name, "setcurrentNetMap") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "maplist"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if (listPtr) + { + trap_Cvar_Set("ui_currentNetMap", va("%d",listPtr->cursorPos)); + } + } + } + } + else if (Q_stricmp(name, "resetmaplist") == 0) + { + menuDef_t *menu; + itemDef_t *item; + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "maplist"); + if (item) + { + uiInfo.uiDC.feederSelection(item->special, item->cursorPos, item); + } + } + } + else if (Q_stricmp(name, "getmousepitch") == 0) + { + trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1"); + } + else if (Q_stricmp(name, "clampmaxplayers") == 0) + { + UI_ClampMaxPlayers(); + } +#ifdef _XBOX + // XBL uiScript commands + else if (Q_stricmp(name, "initaccountlist") == 0) + { + // Make sure that things are up and running + XBL_Init(); + } + else if (Q_stricmp(name, "createaccount") == 0) + { + UI_xboxErrorPopup( XB_POPUP_CONFIRM_NEW_ACCOUNT ); + } + else if (Q_stricmp(name, "logonlive") == 0) + { + // We've already called SetAccountIndex somewhere + // We don't even check for return value - this just kicks off a + // giant sequence of popups and such, ending with the lobby + XBL_Login( LOGIN_PASSCODE_CHECK ); + } + else if (Q_stricmp(name, "logofflive") == 0) + { + // User is already logged on - is trying to back out. Get confirmation + UI_xboxErrorPopup( XB_POPUP_CONFIRM_LOGOFF ); + } + else if (Q_stricmp(name, "quickmatch") == 0) + { + // VVFIXME - Someone needs to handle quickmatch resulting UI (errors) + // Probably done within XBL_MM_QuickMatch() though... + if (XBL_MM_QuickMatch()) + { + // QuickMatch code has already issued a connect... + Menus_CloseAll(); + } + } + else if (Q_stricmp(name, "optimatch") == 0) + { + // Run the query + XBL_MM_Find_Session( + (ui_optiGameType.integer >= 0) ? ui_optiGameType.integer : X_MATCH_NULL_INTEGER, + (ui_optiCurrentMap.integer >= 0) ? ui_optiCurrentMap.integer : X_MATCH_NULL_INTEGER, + ui_optiMinPlayers.integer, + ui_optiMaxPlayers.integer, + (ui_optiFriendlyFire.integer >= 0) ? ui_optiFriendlyFire.integer : X_MATCH_NULL_INTEGER, + (ui_optiJediMastery.integer >= 0) ? ui_optiJediMastery.integer : X_MATCH_NULL_INTEGER, + (ui_optiSaberOnly.integer >= 0) ? ui_optiSaberOnly.integer : X_MATCH_NULL_INTEGER); + + // Everything else is handled automatically. XBL_MM_Tick is getting pings for us, + // results already exists and will be pulled by the listbox drawing code. + Menus_CloseAll(); + Menus_OpenByName("optimatch_results"); + } + else if (Q_stricmp(name, "haltoptimatch") == 0) + { + // Cancels probing of QoS from results once we've backed out or started joining a server + XBL_MM_CancelProbing(); + } + 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, "singleplayer") == 0) + { + extern void Sys_Reboot( const char *reason ); + Sys_Reboot("singleplayer"); + } + else if (Q_stricmp(name, "plyrList") == 0) + { + // Handles all player list functionality as secondary commands + UI_XBL_PlayerListScript(args, name); + } + else if (Q_stricmp(name, "friendsList") == 0) + { + // Handles all friends list functionality as secondary commands + UI_XBL_FriendsListScript(args, name); + } + else if (Q_stricmp(name, "getvoicemask") == 0) + { + // Retrieve current voice mask value for UI display + trap_Cvar_SetValue( "ui_voiceMask", g_Voice.GetVoiceMask() ); + } + else if (Q_stricmp(name, "setvoicemask") == 0) + { + // Update voice mask being used from UI version + g_Voice.SetVoiceMask( trap_Cvar_VariableValue( "ui_voiceMask" ) ); + } +#endif + else + { + Com_Printf("unknown UI script %s\n", name); + } + } +} + +static void UI_GetTeamColor(vec4_t *color) { +} + +#include "../namespace_begin.h" +int BG_SiegeCountBaseClass(const int team, const short classIndex); +#include "../namespace_end.h" + +static void UI_SiegeClassCnt( const int team ) +{ + UI_SetSiegeTeams(); + + trap_Cvar_Set("ui_infantry_cnt", va("%d", BG_SiegeCountBaseClass(team,0))); + trap_Cvar_Set("ui_vanguard_cnt", va("%d", BG_SiegeCountBaseClass(team,1))); + trap_Cvar_Set("ui_support_cnt", va("%d", BG_SiegeCountBaseClass(team,2))); + trap_Cvar_Set("ui_jedi_cnt", va("%d", BG_SiegeCountBaseClass(team,3))); + trap_Cvar_Set("ui_demo_cnt", va("%d", BG_SiegeCountBaseClass(team,4))); + trap_Cvar_Set("ui_heavy_cnt", va("%d", BG_SiegeCountBaseClass(team,5))); + +} + +void UI_ClampMaxPlayers(void) +{ + char buf[32]; + // min checks + // + if( uiInfo.gameTypes[ui_netGameType.integer].gtEnum == GT_DUEL ) //DUEL + { + if( trap_Cvar_VariableValue("sv_maxClients") < 2 ) + { + trap_Cvar_Set("sv_maxClients", "2"); + } + } + else if( uiInfo.gameTypes[ui_netGameType.integer].gtEnum == GT_POWERDUEL ) // POWER DUEL + { + if( trap_Cvar_VariableValue("sv_maxClients") < 3 ) + { + trap_Cvar_Set("sv_maxClients", "3"); + } + } + + + // max check for all game types + if( trap_Cvar_VariableValue("sv_maxClients") > MAX_CLIENTS ) + { + sprintf(buf,"%d",MAX_CLIENTS); + trap_Cvar_Set("sv_maxClients", buf); + } + +} + +void UI_UpdateSiegeStatusIcons(void) +{ + menuDef_t *menu = Menu_GetFocused(); + int i; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + for (i=0;i<7;i++) + { + + Menu_SetItemBackground(menu,va("wpnicon0%d",i), va("*ui_class_weapon%d",i)); + } + + for (i=0;i<7;i++) + { + Menu_SetItemBackground(menu,va("itemicon0%d",i), va("*ui_class_item%d",i)); + } + + for (i=0;i<10;i++) + { + Menu_SetItemBackground(menu,va("forceicon0%d",i), va("*ui_class_power%d",i)); + } + + for (i=10;i<15;i++) + { + Menu_SetItemBackground(menu,va("forceicon%d",i), va("*ui_class_power%d",i)); + } + +} + +/* +================== +UI_MapCountByGameType +================== +*/ +static int UI_MapCountByGameType(qboolean singlePlayer) { + int i, c, game; + c = 0; + game = singlePlayer ? uiInfo.gameTypes[ui_gameType.integer].gtEnum : uiInfo.gameTypes[ui_netGameType.integer].gtEnum; + if (game == GT_SINGLE_PLAYER) { + game++; + } + if (game == GT_TEAM) { + game = GT_FFA; + } + if (game == GT_HOLOCRON || game == GT_JEDIMASTER) { + game = GT_FFA; + } + + for (i = 0; i < uiInfo.mapCount; i++) { + uiInfo.mapList[i].active = qfalse; + if ( uiInfo.mapList[i].typeBits & (1 << game)) { + if (singlePlayer) { + if (!(uiInfo.mapList[i].typeBits & (1 << GT_SINGLE_PLAYER))) { + continue; + } + } + c++; + uiInfo.mapList[i].active = qtrue; + } + } + return c; +} + +qboolean UI_hasSkinForBase(const char *base, const char *team) { + char test[1024]; + fileHandle_t f; + + Com_sprintf( test, sizeof( test ), "models/players/%s/%s/lower_default.skin", base, team ); + trap_FS_FOpenFile(test, &f, FS_READ); + if (f != 0) { + trap_FS_FCloseFile(f); + return qtrue; + } + Com_sprintf( test, sizeof( test ), "models/players/characters/%s/%s/lower_default.skin", base, team ); + trap_FS_FOpenFile(test, &f, FS_READ); + if (f != 0) { + trap_FS_FCloseFile(f); + return qtrue; + } + return qfalse; +} + +/* +================== +UI_HeadCountByColor +================== +*/ +static int UI_HeadCountByColor() { + int i, c; + char *teamname; + + c = 0; + + switch(uiSkinColor) + { + case TEAM_BLUE: + teamname = "/blue"; + break; + case TEAM_RED: + teamname = "/red"; + break; + default: + teamname = "/default"; + } + + // Count each head with this color + for (i=0; i uiInfo.serverStatus.numDisplayServers ) { + return; + } + // + uiInfo.serverStatus.numDisplayServers++; + for (i = uiInfo.serverStatus.numDisplayServers; i > position; i--) { + uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i-1]; + } + uiInfo.serverStatus.displayServers[position] = num; +} + +/* +================== +UI_RemoveServerFromDisplayList +================== +*/ +static void UI_RemoveServerFromDisplayList(int num) { + int i, j; + + for (i = 0; i < uiInfo.serverStatus.numDisplayServers; i++) { + if (uiInfo.serverStatus.displayServers[i] == num) { + uiInfo.serverStatus.numDisplayServers--; + for (j = i; j < uiInfo.serverStatus.numDisplayServers; j++) { + uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j+1]; + } + return; + } + } +} + +/* +================== +UI_BinaryServerInsertion +================== +*/ +static void UI_BinaryServerInsertion(int num) { + int mid, offset, res, len; + + // use binary search to insert server + len = uiInfo.serverStatus.numDisplayServers; + mid = len; + offset = 0; + res = 0; + while(mid > 0) { + mid = len >> 1; + // + res = trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, + uiInfo.serverStatus.sortDir, num, uiInfo.serverStatus.displayServers[offset+mid]); + // if equal + if (res == 0) { + UI_InsertServerIntoDisplayList(num, offset+mid); + return; + } + // if larger + else if (res == 1) { + offset += mid; + len -= mid; + } + // if smaller + else { + len -= mid; + } + } + if (res == 1) { + offset++; + } + UI_InsertServerIntoDisplayList(num, offset); +} + +/* +================== +UI_BuildServerDisplayList +================== +*/ +static void UI_BuildServerDisplayList(qboolean force) { + int i, count, clients, maxClients, ping, game, len/*, visible*/; + char info[MAX_STRING_CHARS]; +// qboolean startRefresh = qtrue; TTimo: unused + static int numinvisible; + + if (!(force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh)) { + return; + } + // if we shouldn't reset + if ( force == 2 ) { + force = 0; + } + + // do motd updates here too + trap_Cvar_VariableStringBuffer( "cl_motdString", uiInfo.serverStatus.motd, sizeof(uiInfo.serverStatus.motd) ); + len = strlen(uiInfo.serverStatus.motd); + if (len == 0) { + strcpy(uiInfo.serverStatus.motd, "Welcome to Jedi Academy MP!"); + len = strlen(uiInfo.serverStatus.motd); + } + if (len != uiInfo.serverStatus.motdLen) { + uiInfo.serverStatus.motdLen = len; + uiInfo.serverStatus.motdWidth = -1; + } + + if (force) { + numinvisible = 0; + // clear number of displayed servers + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + // set list box index to zero + Menu_SetFeederSelection(NULL, FEEDER_SERVERS, 0, NULL); + // mark all servers as visible so we store ping updates for them + trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue); + } + + // get the server count (comes from the master) + count = trap_LAN_GetServerCount(ui_netSource.integer); + if (count == -1 || (ui_netSource.integer == AS_LOCAL && count == 0) ) { + // still waiting on a response from the master + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500; + return; + } + +// visible = qfalse; + for (i = 0; i < count; i++) { + // if we already got info for this server + if (!trap_LAN_ServerIsVisible(ui_netSource.integer, i)) { + continue; + } +// visible = qtrue; + // get the ping for this server + ping = trap_LAN_GetServerPing(ui_netSource.integer, i); + if (ping > 0 || ui_netSource.integer == AS_FAVORITES) { + + trap_LAN_GetServerInfo(ui_netSource.integer, i, info, MAX_STRING_CHARS); + + clients = atoi(Info_ValueForKey(info, "clients")); + uiInfo.serverStatus.numPlayersOnServers += clients; + + if (ui_browserShowEmpty.integer == 0) { + if (clients == 0) { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); + continue; + } + } + + if (ui_browserShowFull.integer == 0) { + maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); + if (clients == maxClients) { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); + continue; + } + } + + if (uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum != -1) { + game = atoi(Info_ValueForKey(info, "gametype")); + if (game != uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum) { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); + continue; + } + } + + if (ui_serverFilterType.integer > 0) { + if (Q_stricmp(Info_ValueForKey(info, "game"), serverFilters[ui_serverFilterType.integer].basedir) != 0) { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); + continue; + } + } + // make sure we never add a favorite server twice + if (ui_netSource.integer == AS_FAVORITES) { + UI_RemoveServerFromDisplayList(i); + } + // insert the server into the list + UI_BinaryServerInsertion(i); + // done with this server + if (ping > 0) { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); + numinvisible++; + } + } + else + { + trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse); +// visible = qfalse; + } + } + + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime; + + // if there were no servers visible for ping updates +// if (!visible) { +// UI_StopServerRefresh(); +// uiInfo.serverStatus.nextDisplayRefresh = 0; +// } +} + +typedef struct +{ + char *name, *altName; +} serverStatusCvar_t; + +serverStatusCvar_t serverStatusCvars[] = { + {"sv_hostname", "Name"}, + {"Address", ""}, + {"gamename", "Game name"}, + {"g_gametype", "Game type"}, + {"mapname", "Map"}, + {"version", ""}, + {"protocol", ""}, + {"timelimit", ""}, + {"fraglimit", ""}, + {NULL, NULL} +}; + +/* +================== +UI_SortServerStatusInfo +================== +*/ +static void UI_SortServerStatusInfo( serverStatusInfo_t *info ) { + int i, j, index; + char *tmp1, *tmp2; + + // FIXME: if "gamename" == "base" or "missionpack" then + // replace the gametype number by FFA, CTF etc. + // + index = 0; + for (i = 0; serverStatusCvars[i].name; i++) { + for (j = 0; j < info->numLines; j++) { + if ( !info->lines[j][1] || info->lines[j][1][0] ) { + continue; + } + if ( !Q_stricmp(serverStatusCvars[i].name, info->lines[j][0]) ) { + // swap lines + tmp1 = info->lines[index][0]; + tmp2 = info->lines[index][3]; + info->lines[index][0] = info->lines[j][0]; + info->lines[index][3] = info->lines[j][3]; + info->lines[j][0] = tmp1; + info->lines[j][3] = tmp2; + // + if ( strlen(serverStatusCvars[i].altName) ) { + info->lines[index][0] = serverStatusCvars[i].altName; + } + index++; + } + } + } +} + + +/* +================== +UI_CheckPassword +================== +*/ +static qboolean UI_CheckPassword( void ) +{ + static char info[MAX_STRING_CHARS]; + + int index = uiInfo.serverStatus.currentServer; + if( (index < 0) || (index >= uiInfo.serverStatus.numDisplayServers) ) + { // warning? + return qfalse; + } + + trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); + + if ( atoi(Info_ValueForKey(info, "needpass")) ) + { + + Menus_OpenByName("password_request"); + return qfalse; + + } + + // This isn't going to make it (too late in dev), like James said I should check to see when we receive + // a packet *if* we do indeed get a 0 ping just make it 1 so then a 0 ping is guaranteed to be bad + /* + // also check ping! + ping = atoi(Info_ValueForKey(info, "ping")); + // NOTE : PING -- it's very questionable as to whether a ping of < 0 or <= 0 indicates a bad server + // what I do know, is that getting "ping" from the ServerInfo on a bad server returns 0. + // So I'm left with no choice but to not allow you to enter a server with a ping of 0 + if( ping <= 0 ) + { + Menus_OpenByName("bad_server"); + return qfalse; + } + */ + + + return qtrue; + + +} + +/* +================== +UI_JoinServer +================== +*/ +static void UI_JoinServer( void ) +{ + char buff[1024]; + + trap_Cvar_Set("cg_thirdPerson", "0"); + trap_Cvar_Set("cg_cameraOrbit", "0"); + trap_Cvar_Set("ui_singlePlayerActive", "0"); +#ifdef _XBOX + if (logged_on) + { // Live server + XBL_MM_JoinServer( uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer] ); + } + else + { // System link + SysLink_JoinServer( uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer] ); + } +#else + if (uiInfo.serverStatus.currentServer >= 0 && uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers) + { + trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, 1024); + trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", buff ) ); + } +#endif + +} + + + +/* +================== +UI_CheckServerName +================== +*/ +static void UI_CheckServerName( void ) +{ + qboolean changed = qfalse; + + char hostname[MAX_INFO_STRING]; + char *c = hostname; + + trap_Cvar_VariableStringBuffer( "sv_hostname", hostname, MAX_INFO_STRING ); + + while( *c ) + { + if ( (*c == '\\') || (*c == ';') || (*c == '"')) + { + *c = '.'; + changed = qtrue; + } + c++; + } + if( changed ) + { + trap_Cvar_Set("sv_hostname", hostname ); + } + +} + + +/* +================== +UI_GetServerStatusInfo +================== +*/ +static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info ) { + char *p, *score, *ping, *name; + int i, len; + + if (!info) { + trap_LAN_ServerStatus( serverAddress, NULL, 0); + return qfalse; + } + memset(info, 0, sizeof(*info)); + if ( trap_LAN_ServerStatus( serverAddress, info->text, sizeof(info->text)) ) { + Q_strncpyz(info->address, serverAddress, sizeof(info->address)); + p = info->text; + info->numLines = 0; + info->lines[info->numLines][0] = "Address"; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + info->lines[info->numLines][3] = info->address; + info->numLines++; + // get the cvars + while (p && *p) { + p = strchr(p, '\\'); + if (!p) break; + *p++ = '\0'; + if (*p == '\\') + break; + info->lines[info->numLines][0] = p; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + p = strchr(p, '\\'); + if (!p) break; + *p++ = '\0'; + info->lines[info->numLines][3] = p; + + info->numLines++; + if (info->numLines >= MAX_SERVERSTATUS_LINES) + break; + } + // get the player list + if (info->numLines < MAX_SERVERSTATUS_LINES-3) { + // empty line + info->lines[info->numLines][0] = ""; + info->lines[info->numLines][1] = ""; + info->lines[info->numLines][2] = ""; + info->lines[info->numLines][3] = ""; + info->numLines++; + // header + info->lines[info->numLines][0] = "num"; + info->lines[info->numLines][1] = "score"; + info->lines[info->numLines][2] = "ping"; + info->lines[info->numLines][3] = "name"; + info->numLines++; + // parse players + i = 0; + len = 0; + while (p && *p) { + if (*p == '\\') + *p++ = '\0'; + if (!p) + break; + score = p; + p = strchr(p, ' '); + if (!p) + break; + *p++ = '\0'; + ping = p; + p = strchr(p, ' '); + if (!p) + break; + *p++ = '\0'; + name = p; + Com_sprintf(&info->pings[len], sizeof(info->pings)-len, "%d", i); + info->lines[info->numLines][0] = &info->pings[len]; + len += strlen(&info->pings[len]) + 1; + info->lines[info->numLines][1] = score; + info->lines[info->numLines][2] = ping; + info->lines[info->numLines][3] = name; + info->numLines++; + if (info->numLines >= MAX_SERVERSTATUS_LINES) + break; + p = strchr(p, '\\'); + if (!p) + break; + *p++ = '\0'; + // + i++; + } + } + UI_SortServerStatusInfo( info ); + return qtrue; + } + return qfalse; +} + +/* +================== +stristr +================== +*/ +static char *stristr(char *str, char *charset) { + int i; + + while(*str) { + for (i = 0; charset[i] && str[i]; i++) { + if (toupper(charset[i]) != toupper(str[i])) break; + } + if (!charset[i]) return str; + str++; + } + return NULL; +} + +/* +================== +UI_BuildFindPlayerList +================== +*/ +static void UI_BuildFindPlayerList(qboolean force) { + static int numFound, numTimeOuts; + int i, j, resend; + serverStatusInfo_t info; + char name[MAX_NAME_LENGTH+2]; + char infoString[MAX_STRING_CHARS]; + + if (!force) { + if (!uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime) { + return; + } + } + else { + memset(&uiInfo.pendingServerStatus, 0, sizeof(uiInfo.pendingServerStatus)); + uiInfo.numFoundPlayerServers = 0; + uiInfo.currentFoundPlayerServer = 0; + trap_Cvar_VariableStringBuffer( "ui_findPlayer", uiInfo.findPlayerName, sizeof(uiInfo.findPlayerName)); + Q_CleanStr(uiInfo.findPlayerName); + // should have a string of some length + if (!strlen(uiInfo.findPlayerName)) { + uiInfo.nextFindPlayerRefresh = 0; + return; + } + // set resend time + resend = ui_serverStatusTimeOut.integer / 2 - 10; + if (resend < 50) { + resend = 50; + } + trap_Cvar_Set("cl_serverStatusResendTime", va("%d", resend)); + // reset all server status requests + trap_LAN_ServerStatus( NULL, NULL, 0); + // + uiInfo.numFoundPlayerServers = 1; + + trap_SP_GetStringTextString("MENUS_SEARCHING", holdSPString, sizeof(holdSPString)); + trap_Cvar_Set( "ui_playerServersFound", va( holdSPString,uiInfo.pendingServerStatus.num, numFound)); + // Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + // sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), + // "searching %d...", uiInfo.pendingServerStatus.num); + numFound = 0; + numTimeOuts++; + } + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + // if this pending server is valid + if (uiInfo.pendingServerStatus.server[i].valid) { + // try to get the server status for this server + if (UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, &info ) ) { + // + numFound++; + // parse through the server status lines + for (j = 0; j < info.numLines; j++) { + // should have ping info + if ( !info.lines[j][2] || !info.lines[j][2][0] ) { + continue; + } + // clean string first + Q_strncpyz(name, info.lines[j][3], sizeof(name)); + Q_CleanStr(name); + // if the player name is a substring + if (stristr(name, uiInfo.findPlayerName)) { + // add to found server list if we have space (always leave space for a line with the number found) + if (uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS-1) { + // + Q_strncpyz(uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers-1], + uiInfo.pendingServerStatus.server[i].adrstr, + sizeof(uiInfo.foundPlayerServerAddresses[0])); + Q_strncpyz(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + uiInfo.pendingServerStatus.server[i].name, + sizeof(uiInfo.foundPlayerServerNames[0])); + uiInfo.numFoundPlayerServers++; + } + else { + // can't add any more so we're done + uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers; + } + } + } + + trap_SP_GetStringTextString("MENUS_SEARCHING", holdSPString, sizeof(holdSPString)); + trap_Cvar_Set( "ui_playerServersFound", va( holdSPString,uiInfo.pendingServerStatus.num, numFound)); + // Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + // sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), + // "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound); + // retrieved the server status so reuse this spot + uiInfo.pendingServerStatus.server[i].valid = qfalse; + } + } + // if empty pending slot or timed out + if (!uiInfo.pendingServerStatus.server[i].valid || + uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer) { + if (uiInfo.pendingServerStatus.server[i].valid) { + numTimeOuts++; + } + // reset server status request for this address + UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, NULL ); + // reuse pending slot + uiInfo.pendingServerStatus.server[i].valid = qfalse; + // if we didn't try to get the status of all servers in the main browser yet + if (uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers) { + uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime; + trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], + uiInfo.pendingServerStatus.server[i].adrstr, sizeof(uiInfo.pendingServerStatus.server[i].adrstr)); + trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], infoString, sizeof(infoString)); + Q_strncpyz(uiInfo.pendingServerStatus.server[i].name, Info_ValueForKey(infoString, "hostname"), sizeof(uiInfo.pendingServerStatus.server[0].name)); + uiInfo.pendingServerStatus.server[i].valid = qtrue; + uiInfo.pendingServerStatus.num++; + + trap_SP_GetStringTextString("MENUS_SEARCHING", holdSPString, sizeof(holdSPString)); + trap_Cvar_Set( "ui_playerServersFound", va( holdSPString,uiInfo.pendingServerStatus.num, numFound)); + + // Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], + // sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]), + // "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound); + } + } + } + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if (uiInfo.pendingServerStatus.server[i].valid) { + break; + } + } + // if still trying to retrieve server status info + if (i < MAX_SERVERSTATUSREQUESTS) { + uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25; + } + else { + // add a line that shows the number of servers found + if (!uiInfo.numFoundPlayerServers) + { + Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]), "no servers found"); + } + else + { + trap_SP_GetStringTextString("MENUS_SERVERS_FOUNDWITH", holdSPString, sizeof(holdSPString)); + trap_Cvar_Set( "ui_playerServersFound", va( holdSPString, + uiInfo.numFoundPlayerServers-1, + uiInfo.numFoundPlayerServers == 2 ? "":"s", + uiInfo.findPlayerName) ); + } + uiInfo.nextFindPlayerRefresh = 0; + // show the server status info for the selected server + UI_FeederSelection(FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer, NULL); + } +} + +/* +================== +UI_BuildServerStatus +================== +*/ +static void UI_BuildServerStatus(qboolean force) { + + if (uiInfo.nextFindPlayerRefresh) { + return; + } + if (!force) { + if (!uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime) { + return; + } + } + else { + Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL); + uiInfo.serverStatusInfo.numLines = 0; + // reset all server status requests + trap_LAN_ServerStatus( NULL, NULL, 0); + } + if (uiInfo.serverStatus.currentServer < 0 || uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || uiInfo.serverStatus.numDisplayServers == 0) { + return; + } + if (UI_GetServerStatusInfo( uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo ) ) { + uiInfo.nextServerStatusRefresh = 0; + UI_GetServerStatusInfo( uiInfo.serverStatusAddress, NULL ); + } + else { + uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500; + } +} + +int UI_SiegeClassNum(siegeClass_t *scl) +{ + int i = 0; + + while (i < bgNumSiegeClasses) + { + if (&bgSiegeClasses[i] == scl) + { + return i; + } + i++; + } + + return 0; +} + +void UI_SetSiegeTeams(void) +{ + char info[MAX_INFO_VALUE]; + char *mapname = NULL; + char levelname[MAX_QPATH]; + char btime[1024]; + char teams[2048]; + char teamInfo[MAX_SIEGE_INFO_SIZE]; + char team1[1024]; + char team2[1024]; + int len = 0; + int gametype; + fileHandle_t f; + + //Get the map name from the server info + if (trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) )) + { + mapname = Info_ValueForKey( info, "mapname" ); + } + + if (!mapname || !mapname[0]) + { + return; + } + + gametype = atoi(Info_ValueForKey(info, "g_gametype")); + + //If the server we are connected to is not siege we cannot choose a class anyway + if (gametype != GT_SIEGE) + { + return; + } + + Com_sprintf(levelname, sizeof(levelname), "maps/%s.siege", mapname); + + if (!levelname || !levelname[0]) + { + return; + } + + len = trap_FS_FOpenFile(levelname, &f, FS_READ); + + if (!f || len >= MAX_SIEGE_INFO_SIZE) + { + return; + } + + trap_FS_Read(siege_info, len, f); + siege_info[len] = 0; //ensure null terminated + + trap_FS_FCloseFile(f); + + //Found the .siege file + + 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); + } + + trap_Cvar_VariableStringBuffer("cg_siegeTeam2", buf, 1024); + if (buf[0] && Q_stricmp(buf, "none")) + { + strcpy(team2, buf); + } + else + { + BG_SiegeGetPairedValue(teams, "team2", team2); + } + } + else + { + return; + } + + //Set the team themes so we know what classes to make available for selection + if (BG_SiegeGetValueGroup(siege_info, team1, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "UseTeam", btime)) + { + BG_SiegeSetTeamTheme(SIEGETEAM_TEAM1, btime); + } + } + if (BG_SiegeGetValueGroup(siege_info, team2, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "UseTeam", btime)) + { + BG_SiegeSetTeamTheme(SIEGETEAM_TEAM2, btime); + } + } + + siegeTeam1 = BG_SiegeFindThemeForTeam(SIEGETEAM_TEAM1); + siegeTeam2 = BG_SiegeFindThemeForTeam(SIEGETEAM_TEAM2); + + //set the default description for the default selection + if (!siegeTeam1 || !siegeTeam1->classes[0]) + { + Com_Error(ERR_DROP, "Error loading teams in UI"); + } + + Menu_SetFeederSelection(NULL, FEEDER_SIEGE_TEAM1, 0, NULL); + Menu_SetFeederSelection(NULL, FEEDER_SIEGE_TEAM2, -1, NULL); +} + +/* +================== +UI_FeederCount +================== +*/ +static int UI_FeederCount(float feederID) +{ + int team,baseClass,count=0,i; + static char info[MAX_STRING_CHARS]; + + switch ( (int)feederID ) + { + + case FEEDER_SABER_SINGLE_INFO: + + for (i=0;inumClasses; + case FEEDER_SIEGE_TEAM2: + if (!siegeTeam2) + { + UI_SetSiegeTeams(); + if (!siegeTeam2) + { + return 0; + } + } + return siegeTeam2->numClasses; + + case FEEDER_FORCECFG: + if (uiForceSide == FORCE_LIGHTSIDE) + { + return uiInfo.forceConfigCount-uiInfo.forceConfigLightIndexBegin; + } + else + { + return uiInfo.forceConfigLightIndexBegin+1; + } + //return uiInfo.forceConfigCount; + + case FEEDER_CINEMATICS: + return uiInfo.movieCount; + + case FEEDER_MAPS: + case FEEDER_ALLMAPS: + return UI_MapCountByGameType(feederID == FEEDER_MAPS ? qtrue : qfalse); + + case FEEDER_SERVERS: + return uiInfo.serverStatus.numDisplayServers; + + case FEEDER_SERVERSTATUS: + return uiInfo.serverStatusInfo.numLines; + + case FEEDER_FINDPLAYER: + return uiInfo.numFoundPlayerServers; + + case FEEDER_PLAYER_LIST: + if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) + { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } + return uiInfo.playerCount; + + case FEEDER_TEAM_LIST: + if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) + { + uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000; + UI_BuildPlayerList(); + } + return uiInfo.myTeamCount; + + case FEEDER_MODS: + return uiInfo.modCount; + + case FEEDER_DEMOS: + return uiInfo.demoCount; + + case FEEDER_MOVES : + + for (i=0;i= SPC_INFANTRY) && (baseClass < SPC_MAX)) + { + return (BG_SiegeCountBaseClass( team, baseClass )); + } + } + return 0; + + // Get the count of weapons + case FEEDER_SIEGE_CLASS_WEAPONS: + //count them up + for (i=0;i< WP_NUM_WEAPONS;i++) + { + trap_Cvar_VariableStringBuffer( va("ui_class_weapon%i", i), info, sizeof(info) ); + if (stricmp(info,"gfx/2d/select")!=0) + { + count++; + } + } + + return count; + + // Get the count of inventory + case FEEDER_SIEGE_CLASS_INVENTORY: + //count them up + for (i=0;i< HI_NUM_HOLDABLE;i++) + { + trap_Cvar_VariableStringBuffer( va("ui_class_item%i", i), info, sizeof(info) ); + // A hack so health and ammo dispenser icons don't show up. + if ((stricmp(info,"gfx/2d/select")!=0) && (stricmp(info,"gfx/hud/i_icon_healthdisp")!=0) && + (stricmp(info,"gfx/hud/i_icon_ammodisp")!=0)) + { + count++; + } + } + return count; + + // Get the count of force powers + case FEEDER_SIEGE_CLASS_FORCE: + //count them up + for (i=0;i< NUM_FORCE_POWERS;i++) + { + trap_Cvar_VariableStringBuffer( va("ui_class_power%i", i), info, sizeof(info) ); + if (stricmp(info,"gfx/2d/select")!=0) + { + count++; + } + } + return count; + +#ifdef _XBOX + // Get the count of xbl accounts + case FEEDER_XBL_ACCOUNTS: + // VVFIXME - Again, SOF2 had all kinds of silliness here. Do we need it? + return XBL_GetNumAccounts( false ); + + // Number of active players, plus number in history list, plus one for divider + case FEEDER_XBL_PLAYERS: + return XBL_PL_GetNumPlayers() + 1; + + // Number of friends + case FEEDER_XBL_FRIENDS: + return XBL_F_GetNumFriends(); + + // Number of results from an optimatch query + case FEEDER_XBL_SERVERS: + return XBL_MM_GetNumServers(); +#endif + } + + return 0; +} + +static const char *UI_SelectedMap(int index, int *actual) { + int i, c; + c = 0; + *actual = 0; + + for (i = 0; i < uiInfo.mapCount; i++) { + if (uiInfo.mapList[i].active) { + if (c == index) { + *actual = i; + return uiInfo.mapList[i].mapName; + } else { + c++; + } + } + } + return ""; +} + +/* +================== +UI_HeadCountByColor +================== +*/ +static const char *UI_SelectedTeamHead(int index, int *actual) { + char *teamname; + int i,c=0; + + switch(uiSkinColor) + { + case TEAM_BLUE: + teamname = "/blue"; + break; + case TEAM_RED: + teamname = "/red"; + break; + default: + teamname = "/default"; + break; + } + + // Count each head with this color + + for (i=0; iclasses[index]) + { + return siegeTeam1->classes[index]->name; + } + return ""; + */ + } + else if (feederID == FEEDER_SIEGE_TEAM2) + { + return ""; //nothing I guess, the description part can cover this + /* + if (!siegeTeam1) + { + UI_SetSiegeTeams(); + if (!siegeTeam1) + { + return ""; + } + } + + if (siegeTeam2->classes[index]) + { + return siegeTeam2->classes[index]->name; + } + return ""; + */ + } + else if (feederID == FEEDER_FORCECFG) { + if (index >= 0 && index < uiInfo.forceConfigCount) { + if (index == 0) + { //always show "custom" + return uiInfo.forceConfigNames[index]; + } + else + { + if (uiForceSide == FORCE_LIGHTSIDE) + { + index += uiInfo.forceConfigLightIndexBegin; + if (index < 0) + { + return NULL; + } + if (index >= uiInfo.forceConfigCount) + { + return NULL; + } + return uiInfo.forceConfigNames[index]; + } + else if (uiForceSide == FORCE_DARKSIDE) + { + index += uiInfo.forceConfigDarkIndexBegin; + if (index < 0) + { + return NULL; + } + if (index > uiInfo.forceConfigLightIndexBegin) + { //dark gets read in before light + return NULL; + } + if (index >= uiInfo.forceConfigCount) + { + return NULL; + } + return uiInfo.forceConfigNames[index]; + } + else + { + return NULL; + } + } + } + } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) { + int actual; + return UI_SelectedMap(index, &actual); + } else if (feederID == FEEDER_SERVERS) { + if (index >= 0 && index < uiInfo.serverStatus.numDisplayServers) { + int ping, game; + if (lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000) { + trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); + lastColumn = column; + lastTime = uiInfo.uiDC.realTime; + } + ping = atoi(Info_ValueForKey(info, "ping")); + if (ping == -1) { + // if we ever see a ping that is out of date, do a server refresh + // UI_UpdatePendingPings(); + } + switch (column) { + case SORT_HOST : + if (ping <= 0) { + return Info_ValueForKey(info, "addr"); + } else { + int gametype = 0; + //check for password + if ( atoi(Info_ValueForKey(info, "needpass")) ) + { + *handle3 = uiInfo.uiDC.Assets.needPass; + } + //check for saberonly and restricted force powers + gametype = atoi(Info_ValueForKey(info, "gametype")); + if ( gametype != GT_JEDIMASTER ) + { + qboolean saberOnly = qtrue; + qboolean restrictedForce = qfalse; + qboolean allForceDisabled = qfalse; + int wDisable, i = 0; + + //check force + restrictedForce = atoi(Info_ValueForKey(info, "fdisable")); + if ( UI_AllForceDisabled( restrictedForce ) ) + {//all force powers are disabled + allForceDisabled = qtrue; + *handle2 = uiInfo.uiDC.Assets.noForce; + } + else if ( restrictedForce ) + {//at least one force power is disabled + *handle2 = uiInfo.uiDC.Assets.forceRestrict; + } + + //check weaps + wDisable = atoi(Info_ValueForKey(info, "wdisable")); + + while ( i < WP_NUM_WEAPONS ) + { + if ( !(wDisable & (1 << i)) && i != WP_SABER && i != WP_NONE ) + { + saberOnly = qfalse; + } + + i++; + } + if ( saberOnly ) + { + *handle1 = uiInfo.uiDC.Assets.saberOnly; + } + else if ( atoi(Info_ValueForKey(info, "truejedi")) != 0 ) + { + if ( gametype != GT_HOLOCRON + && gametype != GT_JEDIMASTER + && !saberOnly + && !allForceDisabled ) + {//truejedi is on and allowed in this mode + *handle1 = uiInfo.uiDC.Assets.trueJedi; + } + } + } + if ( ui_netSource.integer == AS_LOCAL ) { + Com_sprintf( hostname, sizeof(hostname), "%s [%s]", + Info_ValueForKey(info, "hostname"), + netnames[atoi(Info_ValueForKey(info, "nettype"))] ); + return hostname; + } + else { + if (atoi(Info_ValueForKey(info, "sv_allowAnonymous")) != 0) { // anonymous server + Com_sprintf( hostname, sizeof(hostname), "(A) %s", + Info_ValueForKey(info, "hostname")); + } else { + Com_sprintf( hostname, sizeof(hostname), "%s", + Info_ValueForKey(info, "hostname")); + } + return hostname; + } + } + case SORT_MAP : + return Info_ValueForKey(info, "mapname"); + case SORT_CLIENTS : + Com_sprintf( clientBuff, sizeof(clientBuff), "%s (%s)", Info_ValueForKey(info, "clients"), Info_ValueForKey(info, "sv_maxclients")); + return clientBuff; + case SORT_GAME : + game = atoi(Info_ValueForKey(info, "gametype")); + if (game >= 0 && game < numTeamArenaGameTypes) { + strcpy(needPass,teamArenaGameTypes[game]); + } else { + if (ping <= 0) + { + strcpy(needPass,"Inactive"); + } + strcpy(needPass,"Unknown"); + } + + return needPass; + case SORT_PING : + if (ping <= 0) { + return "..."; + } else { + return Info_ValueForKey(info, "ping"); + } + } + } + } else if (feederID == FEEDER_SERVERSTATUS) { + if ( index >= 0 && index < uiInfo.serverStatusInfo.numLines ) { + if ( column >= 0 && column < 4 ) { + return uiInfo.serverStatusInfo.lines[index][column]; + } + } + } else if (feederID == FEEDER_FINDPLAYER) { + if ( index >= 0 && index < uiInfo.numFoundPlayerServers ) { + //return uiInfo.foundPlayerServerAddresses[index]; + return uiInfo.foundPlayerServerNames[index]; + } + } else if (feederID == FEEDER_PLAYER_LIST) { + if (index >= 0 && index < uiInfo.playerCount) { + return uiInfo.playerNames[index]; + } + } else if (feederID == FEEDER_TEAM_LIST) { + if (index >= 0 && index < uiInfo.myTeamCount) { + return uiInfo.teamNames[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_CINEMATICS) { + if (index >= 0 && index < uiInfo.movieCount) { + return uiInfo.movieList[index]; + } + } else if (feederID == FEEDER_DEMOS) { + if (index >= 0 && index < uiInfo.demoCount) { + return uiInfo.demoList[index]; + } + } + 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) + { + return 0; + } + else if (feederID == FEEDER_COLORCHOICES) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorCount) + { + *handle1 = trap_R_RegisterShaderNoMip( uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorShader[index]); + return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorShader[index]; + } + } + else if (feederID == FEEDER_PLAYER_SKIN_HEAD) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadCount) + { + *handle1 = trap_R_RegisterShaderNoMip(va("models/players/%s/icon_%s", 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) + { + *handle1 = trap_R_RegisterShaderNoMip(va("models/players/%s/icon_%s", 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) + { + *handle1 = trap_R_RegisterShaderNoMip(va("models/players/%s/icon_%s", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].Name, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[index])); + return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[index]; + } + } + else if (feederID == FEEDER_SIEGE_BASE_CLASS) + { + return ""; + } + else if (feederID == FEEDER_SIEGE_CLASS_WEAPONS) + { + return ""; + } +#ifdef _XBOX + else if (feederID == FEEDER_XBL_ACCOUNTS) + { + // VVFIXME - SOF2 keeps track of old number of accounts, to force a + // refresh when someone yanks an MU. Probably necessary + int numAccounts = XBL_GetNumAccounts( false ); + if (index >= 0 && index < numAccounts) + { + XONLINE_USER *pUser = XBL_GetUserInfo( index ); + if (pUser) + { + static char displayName[XONLINE_GAMERTAG_SIZE]; + strcpy( displayName, pUser->szGamertag ); + return displayName; + } + } + } + else if (feederID == FEEDER_XBL_PLAYERS) + { + int numEntries = XBL_PL_GetNumPlayers() + 1; + + if (index >= 0 && index < numEntries) + { + if (column == 0) + return XBL_PL_GetPlayerName( index ); + else if (column == 1) + return XBL_PL_GetStatusIcon( index ); + else if (column == 2) + return XBL_PL_GetVoiceIcon( index ); + else + return ""; + } + } + else if (feederID == FEEDER_XBL_FRIENDS) + { + if (index >= 0 && index < XBL_F_GetNumFriends()) + { + if (column == 0) + return XBL_F_GetFriendName( index ); + else if (column == 1) + return XBL_F_GetStatusIcon( index ); + else if (column == 2) + return XBL_F_GetVoiceIcon( index ); + else + return ""; + } + } + else if (feederID == FEEDER_XBL_SERVERS) + { + // We handle the optimatch results listbox separately from the rest + // of the UI server browser code. It's just nasty otherwise. + if (index >= 0 && index < XBL_MM_GetNumServers()) + { + switch (column) + { + case SORT_HOST: + return XBL_MM_GetServerName( index ); + case SORT_MAP: + return XBL_MM_GetServerMap( index ); + case SORT_CLIENTS: + return XBL_MM_GetServerClients( index ); + case SORT_GAME: + return XBL_MM_GetServerGametype( index ); + case SORT_PING: + return XBL_MM_GetServerPing( index ); + } + } + } +#endif + + return ""; +} + + +static qhandle_t UI_FeederItemImage(float feederID, int index) { + int validCnt,i; + static char info[MAX_STRING_CHARS]; + + if (feederID == FEEDER_SABER_SINGLE_INFO) + { + return 0; + } + else if (feederID == FEEDER_SABER_STAFF_INFO) + { + return 0; + } + else if (feederID == FEEDER_Q3HEADS) + { + int actual; + UI_SelectedTeamHead(index, &actual); + index = actual; + + if (index >= 0 && index < uiInfo.q3HeadCount) + { //we want it to load them as it draws them, like the TA feeder + //return uiInfo.q3HeadIcons[index]; + int selModel = trap_Cvar_VariableValue("ui_selectedModelIndex"); + + if (selModel != -1) + { + if (uiInfo.q3SelectedHead != selModel) + { + uiInfo.q3SelectedHead = selModel; + //UI_FeederSelection(FEEDER_Q3HEADS, uiInfo.q3SelectedHead); + Menu_SetFeederSelection(NULL, FEEDER_Q3HEADS, selModel, NULL); + } + } + + if (!uiInfo.q3HeadIcons[index]) + { //this isn't the best way of doing this I guess, but I didn't want a whole seperate string array + //for storing shader names. I can't just replace q3HeadNames with the shader name, because we + //print what's in q3HeadNames and the icon name would look funny. + char iconNameFromSkinName[256]; + int i = 0; + int skinPlace; + + i = strlen(uiInfo.q3HeadNames[index]); + + while (uiInfo.q3HeadNames[index][i] != '/') + { + i--; + } + + i++; + skinPlace = i; //remember that this is where the skin name begins + + //now, build a full path out of what's in q3HeadNames, into iconNameFromSkinName + Com_sprintf(iconNameFromSkinName, sizeof(iconNameFromSkinName), "models/players/%s", uiInfo.q3HeadNames[index]); + + i = strlen(iconNameFromSkinName); + + while (iconNameFromSkinName[i] != '/') + { + i--; + } + + i++; + iconNameFromSkinName[i] = 0; //terminate, and append.. + Q_strcat(iconNameFromSkinName, 256, "icon_"); + + //and now, for the final step, append the skin name from q3HeadNames onto the end of iconNameFromSkinName + i = strlen(iconNameFromSkinName); + + while (uiInfo.q3HeadNames[index][skinPlace]) + { + iconNameFromSkinName[i] = uiInfo.q3HeadNames[index][skinPlace]; + i++; + skinPlace++; + } + iconNameFromSkinName[i] = 0; + + //and now we are ready to register (thankfully this will only happen once) + uiInfo.q3HeadIcons[index] = trap_R_RegisterShaderNoMip(iconNameFromSkinName); + } + return uiInfo.q3HeadIcons[index]; + } + } + else if (feederID == FEEDER_SIEGE_TEAM1) + { + if (!siegeTeam1) + { + UI_SetSiegeTeams(); + if (!siegeTeam1) + { + return 0; + } + } + + if (siegeTeam1->classes[index]) + { + return siegeTeam1->classes[index]->uiPortraitShader; + } + return 0; + } + else if (feederID == FEEDER_SIEGE_TEAM2) + { + if (!siegeTeam2) + { + UI_SetSiegeTeams(); + if (!siegeTeam2) + { + return 0; + } + } + + if (siegeTeam2->classes[index]) + { + return siegeTeam2->classes[index]->uiPortraitShader; + } + return 0; + } + 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; + } + } + else if (feederID == FEEDER_PLAYER_SKIN_HEAD) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadCount) + { + //return uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadIcons[index]; + return trap_R_RegisterShaderNoMip(va("models/players/%s/icon_%s", 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 trap_R_RegisterShaderNoMip(va("models/players/%s/icon_%s", 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 trap_R_RegisterShaderNoMip(va("models/players/%s/icon_%s", 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 trap_R_RegisterShaderNoMip( uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorShader[index]); + } + } + + else if ( feederID == FEEDER_SIEGE_BASE_CLASS) + { + int team,baseClass; + + team = (int)trap_Cvar_VariableValue("ui_team"); + baseClass = (int)trap_Cvar_VariableValue("ui_siege_class"); + + if ((team == SIEGETEAM_TEAM1) || + (team == SIEGETEAM_TEAM2)) + { + // Is it a valid base class? + if ((baseClass >= SPC_INFANTRY) && (baseClass < SPC_MAX)) + { + if (index >= 0) + { + return(BG_GetUIPortrait(team, baseClass, index)); + } + } + } + } + else if ( feederID == FEEDER_SIEGE_CLASS_WEAPONS) + { + validCnt = 0; + //count them up + for (i=0;i< WP_NUM_WEAPONS;i++) + { + trap_Cvar_VariableStringBuffer( va("ui_class_weapon%i", i), info, sizeof(info) ); + if (stricmp(info,"gfx/2d/select")!=0) + { + if (validCnt == index) + { + return(trap_R_RegisterShaderNoMip(info)); + } + validCnt++; + } + } + } + else if ( feederID == FEEDER_SIEGE_CLASS_INVENTORY) + { + validCnt = 0; + //count them up + for (i=0;i< HI_NUM_HOLDABLE;i++) + { + trap_Cvar_VariableStringBuffer( va("ui_class_item%i", i), info, sizeof(info) ); + // A hack so health and ammo dispenser icons don't show up. + if ((stricmp(info,"gfx/2d/select")!=0) && (stricmp(info,"gfx/hud/i_icon_healthdisp")!=0) && + (stricmp(info,"gfx/hud/i_icon_ammodisp")!=0)) + { + if (validCnt == index) + { + return(trap_R_RegisterShaderNoMip(info)); + } + validCnt++; + } + } + } + else if ( feederID == FEEDER_SIEGE_CLASS_FORCE) + { + int slotI=0; + static char info2[MAX_STRING_CHARS]; + menuDef_t *menu; + itemDef_t *item; + + + validCnt = 0; + + + menu = Menu_GetFocused(); // Get current menu + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "base_class_force_feed"); + if (item) + { + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; + if (listPtr) + { + slotI = listPtr->startPos; + } + } + } + + //count them up + for (i=0;i< NUM_FORCE_POWERS;i++) + { + trap_Cvar_VariableStringBuffer( va("ui_class_power%i", i), info, sizeof(info) ); + if (stricmp(info,"gfx/2d/select")!=0) + { + if (validCnt == index) + { + trap_Cvar_VariableStringBuffer( va("ui_class_powerlevel%i", validCnt), info2, sizeof(info2) ); + + trap_Cvar_Set(va("ui_class_powerlevelslot%i", index-slotI), info2); + return(trap_R_RegisterShaderNoMip(info)); + } + validCnt++; + } + } + } + + return 0; +} + + + +//called every time a class is selected from a feeder, sets info +//for shaders to be displayed in the menu about the class -rww +extern qboolean UI_SaberTypeForSaber( const char *saberName, char *saberType ); +void UI_SiegeSetCvarsForClass(siegeClass_t *scl) +{ + int i = 0; + int count = 0; + char shader[MAX_QPATH]; + + //let's clear the things out first + while (i < WP_NUM_WEAPONS) + { + trap_Cvar_Set(va("ui_class_weapon%i", i), "gfx/2d/select"); + i++; + } + //now for inventory items + i = 0; + while (i < HI_NUM_HOLDABLE) + { + trap_Cvar_Set(va("ui_class_item%i", i), "gfx/2d/select"); + i++; + } + //now for force powers + i = 0; + while (i < NUM_FORCE_POWERS) + { + trap_Cvar_Set(va("ui_class_power%i", i), "gfx/2d/select"); + i++; + } + + //now health and armor + trap_Cvar_Set("ui_class_health", "0"); + trap_Cvar_Set("ui_class_armor", "0"); + + trap_Cvar_Set("ui_class_icon", ""); + + if (!scl) + { //no select? + return; + } + + //set cvars for which weaps we have + i = 0; + trap_Cvar_Set(va("ui_class_weapondesc%i", count), " "); // Blank it out to start with + while (i < WP_NUM_WEAPONS) + { + + if (scl->weapons & (1<saber1[0] && + scl->saber2[0]) + { + strcpy(saberType, "gfx/hud/w_icon_duallightsaber"); + } //fixme: need saber data access on ui to determine if staff, "gfx/hud/w_icon_saberstaff" + else + { + char buf[1024]; + if (scl->saber1[0] && UI_SaberTypeForSaber(scl->saber1, buf)) + { + if ( !Q_stricmp( buf, "SABER_STAFF" ) ) + { + strcpy(saberType, "gfx/hud/w_icon_saberstaff"); + } + else + { + strcpy(saberType, "gfx/hud/w_icon_lightsaber"); + } + } + else + { + strcpy(saberType, "gfx/hud/w_icon_lightsaber"); + } + } + + trap_Cvar_Set(va("ui_class_weapon%i", count), saberType); + trap_Cvar_Set(va("ui_class_weapondesc%i", count), "@MENUS_AN_ELEGANT_WEAPON_FOR"); + count++; + trap_Cvar_Set(va("ui_class_weapondesc%i", count), " "); // Blank it out to start with + } + else + { + gitem_t *item = BG_FindItemForWeapon( i ); + trap_Cvar_Set(va("ui_class_weapon%i", count), item->icon); + trap_Cvar_Set(va("ui_class_weapondesc%i", count), item->description); + count++; + trap_Cvar_Set(va("ui_class_weapondesc%i", count), " "); // Blank it out to start with + } + } + + i++; + } + + //now for inventory items + i = 0; + count = 0; + + while (i < HI_NUM_HOLDABLE) + { + if (scl->invenItems & (1<icon); + trap_Cvar_Set(va("ui_class_itemdesc%i", count), item->description); + count++; + } + else + { + trap_Cvar_Set(va("ui_class_itemdesc%i", count), " "); + } + i++; + } + + //now for force powers + i = 0; + count = 0; + + while (i < NUM_FORCE_POWERS) + { + trap_Cvar_Set(va("ui_class_powerlevel%i", i), "0"); // Zero this out to start. + if (i<9) + { + trap_Cvar_Set(va("ui_class_powerlevelslot%i", i), "0"); // Zero this out to start. + } + + if (scl->forcePowerLevels[i]) + { + trap_Cvar_Set(va("ui_class_powerlevel%i", count), va("%i",scl->forcePowerLevels[i])); + trap_Cvar_Set(va("ui_class_power%i", count), HolocronIcons[i]); + count++; + } + + i++; + } + + //now health and armor + trap_Cvar_Set("ui_class_health", va("%i", scl->maxhealth)); + trap_Cvar_Set("ui_class_armor", va("%i", scl->maxarmor)); + trap_Cvar_Set("ui_class_speed", va("%3.2f", scl->speed)); + + //now get the icon path based on the shader index + if (scl->classShader) + { + trap_R_ShaderNameFromIndex(shader, scl->classShader); + } + else + { //no shader + shader[0] = 0; + } + trap_Cvar_Set("ui_class_icon", shader); +} + +int g_siegedFeederForcedSet = 0; + +void UI_UpdateCvarsForClass(const int team,const baseClass,const int index) +{ + siegeClass_t *holdClass=0; + char *holdBuf; + + // Is it a valid team + if ((team == SIEGETEAM_TEAM1) || + (team == SIEGETEAM_TEAM2)) + { + + // Is it a valid base class? + if ((baseClass >= SPC_INFANTRY) && (baseClass < SPC_MAX)) + { + // A valid index? + if ((index>=0) && (index < BG_SiegeCountBaseClass( team, baseClass ))) + { + if (!g_siegedFeederForcedSet) + { + holdClass = BG_GetClassOnBaseClass( team, baseClass, index); + if (holdClass) //clicked a valid item + { + g_UIGloballySelectedSiegeClass = UI_SiegeClassNum(holdClass); + trap_Cvar_Set("ui_classDesc", g_UIClassDescriptions[g_UIGloballySelectedSiegeClass].desc); + g_siegedFeederForcedSet = 1; + Menu_SetFeederSelection(NULL, FEEDER_SIEGE_BASE_CLASS, -1, NULL); + UI_SiegeSetCvarsForClass(holdClass); + + holdBuf = BG_GetUIPortraitFile(team, baseClass, index); + if (holdBuf) + { + trap_Cvar_Set("ui_classPortrait",holdBuf); + } + } + } + g_siegedFeederForcedSet = 0; + } + else + { + trap_Cvar_Set("ui_classDesc", " "); + } + } + } + +} + + +qboolean UI_FeederSelection(float feederFloat, int index, itemDef_t *item) +{ + static char info[MAX_STRING_CHARS]; + const int feederID = feederFloat; + + if (feederID == FEEDER_Q3HEADS) + { + int actual; + UI_SelectedTeamHead(index, &actual); + uiInfo.q3SelectedHead = index; + trap_Cvar_Set("ui_selectedModelIndex", va("%i", index)); + index = actual; + if (index >= 0 && index < uiInfo.q3HeadCount) + { + trap_Cvar_Set( "model", uiInfo.q3HeadNames[index]); + } + } + else if (feederID == FEEDER_MOVES) + { + itemDef_t *item; + menuDef_t *menu; + modelDef_t *modelPtr; + + menu = Menus_FindByName("rulesMenu_moves"); + + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + char modelPath[MAX_QPATH]; + int animRunLength; + + ItemParse_model_g2anim_go( item, datapadMoveData[uiInfo.movesTitleIndex][index].anim ); + + Com_sprintf( modelPath, sizeof( modelPath ), "models/players/%s/model.glm", UI_Cvar_VariableString ( "ui_char_model" ) ); + ItemParse_asset_model_go( item, modelPath, &animRunLength ); + UI_UpdateCharacterSkin(); + + uiInfo.moveAnimTime = uiInfo.uiDC.realTime + animRunLength; + + if (datapadMoveData[uiInfo.movesTitleIndex][index].anim) + { + + // Play sound for anim + if (datapadMoveData[uiInfo.movesTitleIndex][index].sound == MDS_FORCE_JUMP) + { + trap_S_StartLocalSound( uiInfo.uiDC.Assets.moveJumpSound, CHAN_LOCAL ); + } + else if (datapadMoveData[uiInfo.movesTitleIndex][index].sound == MDS_ROLL) + { + trap_S_StartLocalSound( uiInfo.uiDC.Assets.moveRollSound, 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; + } + + trap_S_StartLocalSound( *soundPtr, CHAN_LOCAL ); + } + + if (datapadMoveData[uiInfo.movesTitleIndex][index].desc) + { + trap_Cvar_Set( "ui_move_desc", datapadMoveData[uiInfo.movesTitleIndex][index].desc); + } + } + UI_SaberAttachToChar( item ); + } + } + } + } + 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("rulesMenu_moves"); + + if (menu) + { + item = (itemDef_t *) Menu_FindItemByName((menuDef_t *) menu, "character"); + if (item) + { + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + char modelPath[MAX_QPATH]; + int animRunLength; + + uiInfo.movesBaseAnim = datapadMoveTitleBaseAnims[uiInfo.movesTitleIndex]; + ItemParse_model_g2anim_go( item, uiInfo.movesBaseAnim ); + + Com_sprintf( modelPath, sizeof( modelPath ), "models/players/%s/model.glm", UI_Cvar_VariableString ( "ui_char_model" ) ); + ItemParse_asset_model_go( item, modelPath, &animRunLength ); + + UI_UpdateCharacterSkin(); + + } + } + } + } + else if (feederID == FEEDER_SIEGE_TEAM1) + { + if (!g_siegedFeederForcedSet) + { + g_UIGloballySelectedSiegeClass = UI_SiegeClassNum(siegeTeam1->classes[index]); + trap_Cvar_Set("ui_classDesc", g_UIClassDescriptions[g_UIGloballySelectedSiegeClass].desc); + + //g_siegedFeederForcedSet = 1; + //Menu_SetFeederSelection(NULL, FEEDER_SIEGE_TEAM2, -1, NULL); + + UI_SiegeSetCvarsForClass(siegeTeam1->classes[index]); + } + g_siegedFeederForcedSet = 0; + } + else if (feederID == FEEDER_SIEGE_TEAM2) + { + if (!g_siegedFeederForcedSet) + { + g_UIGloballySelectedSiegeClass = UI_SiegeClassNum(siegeTeam2->classes[index]); + trap_Cvar_Set("ui_classDesc", g_UIClassDescriptions[g_UIGloballySelectedSiegeClass].desc); + + //g_siegedFeederForcedSet = 1; + //Menu_SetFeederSelection(NULL, FEEDER_SIEGE_TEAM2, -1, NULL); + + UI_SiegeSetCvarsForClass(siegeTeam2->classes[index]); + } + g_siegedFeederForcedSet = 0; + } + else if (feederID == FEEDER_FORCECFG) + { + int newindex = index; + + if (uiForceSide == FORCE_LIGHTSIDE) + { + newindex += uiInfo.forceConfigLightIndexBegin; + if (newindex >= uiInfo.forceConfigCount) + { + return qfalse; + } + } + else + { //else dark + newindex += uiInfo.forceConfigDarkIndexBegin; + if (newindex >= uiInfo.forceConfigCount || newindex > uiInfo.forceConfigLightIndexBegin) + { //dark gets read in before light + return qfalse; + } + } + + if (index >= 0 && index < uiInfo.forceConfigCount) + { + UI_ForceConfigHandle(uiInfo.forceConfigSelected, index); + uiInfo.forceConfigSelected = index; + } + } + else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) + { + int actual, map; + const char *checkValid = NULL; + + map = (feederID == FEEDER_ALLMAPS) ? ui_currentNetMap.integer : ui_currentMap.integer; + if (uiInfo.mapList[map].cinematic >= 0) { + trap_CIN_StopCinematic(uiInfo.mapList[map].cinematic); + uiInfo.mapList[map].cinematic = -1; + } + checkValid = UI_SelectedMap(index, &actual); + + if (!checkValid || !checkValid[0]) + { //this isn't a valid map to select, so reselect the current + index = ui_mapIndex.integer; + UI_SelectedMap(index, &actual); + } + + trap_Cvar_Set("ui_mapIndex", va("%d", index)); + gUISelectedMap = index; + ui_mapIndex.integer = index; + + if (feederID == FEEDER_MAPS) { + ui_currentMap.integer = actual; + trap_Cvar_Set("ui_currentMap", va("%d", actual)); + uiInfo.mapList[ui_currentMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); + UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); + //trap_Cvar_Set("ui_opponentModel", uiInfo.mapList[ui_currentMap.integer].opponentName); + //updateOpponentModel = qtrue; + } else { + ui_currentNetMap.integer = actual; + trap_Cvar_Set("ui_currentNetMap", va("%d", actual)); + uiInfo.mapList[ui_currentNetMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); + } + + } else if (feederID == FEEDER_SERVERS) { + const char *mapName = NULL; + uiInfo.serverStatus.currentServer = index; + trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS); + uiInfo.serverStatus.currentServerPreview = trap_R_RegisterShaderNoMip(va("levelshots/%s", Info_ValueForKey(info, "mapname"))); + if (uiInfo.serverStatus.currentServerCinematic >= 0) { + trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); + uiInfo.serverStatus.currentServerCinematic = -1; + } + mapName = Info_ValueForKey(info, "mapname"); + if (mapName && *mapName) { + uiInfo.serverStatus.currentServerCinematic = trap_CIN_PlayCinematic(va("%s.roq", mapName), 0, 0, 0, 0, (CIN_loop | CIN_silent) ); + } + } else if (feederID == FEEDER_SERVERSTATUS) { + // + } else if (feederID == FEEDER_FINDPLAYER) { + uiInfo.currentFoundPlayerServer = index; + // + if ( index < uiInfo.numFoundPlayerServers-1) { + // build a new server status for this server + Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress)); + Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL); + UI_BuildServerStatus(qtrue); + } + } else if (feederID == FEEDER_PLAYER_LIST) { + uiInfo.playerIndex = index; + } else if (feederID == FEEDER_TEAM_LIST) { + uiInfo.teamIndex = index; + } else if (feederID == FEEDER_MODS) { + uiInfo.modIndex = 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; + } + else if (feederID == FEEDER_COLORCHOICES) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorCount) + { + Item_RunScript(item, uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].ColorActionText[index]); + } + } + else if (feederID == FEEDER_PLAYER_SKIN_HEAD) + { + if (index >= 0 && index < uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinHeadCount) + { + trap_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) + { + trap_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) + { + trap_Cvar_Set("ui_char_skin_legs", uiInfo.playerSpecies[uiInfo.playerSpeciesIndex].SkinLegNames[index]); + } + } + else if (feederID == FEEDER_PLAYER_SPECIES) + { + uiInfo.playerSpeciesIndex = index; + } + else if (feederID == FEEDER_LANGUAGES) + { + uiInfo.languageCountIndex = index; + } + else if ( feederID == FEEDER_SIEGE_BASE_CLASS ) + { + int team,baseClass; + + team = (int)trap_Cvar_VariableValue("ui_team"); + baseClass = (int)trap_Cvar_VariableValue("ui_siege_class"); + + UI_UpdateCvarsForClass(team, baseClass, index); + } + else if (feederID == FEEDER_SIEGE_CLASS_WEAPONS) + { +// trap_Cvar_VariableStringBuffer( va("ui_class_weapondesc%i", index), info, sizeof(info) ); +// trap_Cvar_Set( "ui_itemforceinvdesc", info ); + } + else if (feederID == FEEDER_SIEGE_CLASS_INVENTORY) + { +// trap_Cvar_VariableStringBuffer( va("ui_class_itemdesc%i", index), info, sizeof(info) ); +// trap_Cvar_Set( "ui_itemforceinvdesc", info ); + } + else if (feederID == FEEDER_SIEGE_CLASS_FORCE) + { + int i; +// int validCnt = 0; + + trap_Cvar_VariableStringBuffer( va("ui_class_power%i", index), info, sizeof(info) ); + + //count them up + for (i=0;i< NUM_FORCE_POWERS;i++) + { + if (!strcmp(HolocronIcons[i],info)) + { + trap_Cvar_Set( "ui_itemforceinvdesc", forcepowerDesc[i] ); + } + } + } +#ifdef _XBOX + else if (feederID == FEEDER_XBL_ACCOUNTS) + { + XBL_SetAccountIndex( index ); + } + else if (feederID == FEEDER_XBL_PLAYERS) + { + XBL_PL_SetPlayerIndex( index ); + } + else if (feederID == FEEDER_XBL_FRIENDS) + { + XBL_F_SetChosenFriendIndex( index ); + } + else if (feederID == FEEDER_XBL_SERVERS) + { + XBL_MM_SetChosenServerIndex( index ); + } +#endif + + return qtrue; +} + + +static qboolean GameType_Parse(char **p, qboolean join) { + char *token; + + token = COM_ParseExt((const char **)p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + if (join) { + uiInfo.numJoinGameTypes = 0; + } else { + uiInfo.numGameTypes = 0; + } + + while ( 1 ) { + token = COM_ParseExt((const char **)p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + if (token[0] == '{') { + // two tokens per line, character name and sex + if (join) { + if (!String_Parse(p, &uiInfo.joinGameTypes[uiInfo.numJoinGameTypes].gameType) || !Int_Parse(p, &uiInfo.joinGameTypes[uiInfo.numJoinGameTypes].gtEnum)) { + return qfalse; + } + } else { + if (!String_Parse(p, &uiInfo.gameTypes[uiInfo.numGameTypes].gameType) || !Int_Parse(p, &uiInfo.gameTypes[uiInfo.numGameTypes].gtEnum)) { + return qfalse; + } + } + + if (join) { + if (uiInfo.numJoinGameTypes < MAX_GAMETYPES) { + uiInfo.numJoinGameTypes++; + } else { + Com_Printf("Too many net game types, last one replace!\n"); + } + } else { + if (uiInfo.numGameTypes < MAX_GAMETYPES) { + uiInfo.numGameTypes++; + } else { + Com_Printf("Too many game types, last one replace!\n"); + } + } + + token = COM_ParseExt((const char **)p, qtrue); + if (token[0] != '}') { + return qfalse; + } + } + } + return qfalse; +} + +static qboolean MapList_Parse(char **p) { + char *token; + + token = COM_ParseExt((const char **)p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + uiInfo.mapCount = 0; + + while ( 1 ) { + token = COM_ParseExt((const char **)p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + if (token[0] == '{') { + if (!String_Parse(p, &uiInfo.mapList[uiInfo.mapCount].mapName) || !String_Parse(p, &uiInfo.mapList[uiInfo.mapCount].mapLoadName) + ||!Int_Parse(p, &uiInfo.mapList[uiInfo.mapCount].teamMembers) ) { + return qfalse; + } + + if (!String_Parse(p, &uiInfo.mapList[uiInfo.mapCount].opponentName)) { + return qfalse; + } + + uiInfo.mapList[uiInfo.mapCount].typeBits = 0; + + while (1) { + token = COM_ParseExt((const char **)p, qtrue); + if (token[0] >= '0' && token[0] <= '9') { + uiInfo.mapList[uiInfo.mapCount].typeBits |= (1 << (token[0] - 0x030)); + if (!Int_Parse(p, &uiInfo.mapList[uiInfo.mapCount].timeToBeat[token[0] - 0x30])) { + return qfalse; + } + } else { + break; + } + } + + //mapList[mapCount].imageName = String_Alloc(va("levelshots/%s", mapList[mapCount].mapLoadName)); + //if (uiInfo.mapCount == 0) { + // only load the first cinematic, selection loads the others + // uiInfo.mapList[uiInfo.mapCount].cinematic = trap_CIN_PlayCinematic(va("%s.roq",uiInfo.mapList[uiInfo.mapCount].mapLoadName), qfalse, qfalse, qtrue, 0, 0, 0, 0); + //} + uiInfo.mapList[uiInfo.mapCount].cinematic = -1; + uiInfo.mapList[uiInfo.mapCount].levelShot = trap_R_RegisterShaderNoMip(va("levelshots/%s_small", uiInfo.mapList[uiInfo.mapCount].mapLoadName)); + + if (uiInfo.mapCount < MAX_MAPS) { + uiInfo.mapCount++; + } else { + Com_Printf("Too many maps, last one replaced!\n"); + } + } + } + return qfalse; +} + +static void UI_ParseGameInfo(const char *teamFile) { + char *token; + char *p; + char *buff = NULL; + //int mode = 0; TTimo: unused + + buff = GetMenuBuffer(teamFile); + if (!buff) { + return; + } + + p = buff; + + while ( 1 ) { + token = COM_ParseExt( (const char **)(&p), qtrue ); + if( !token || token[0] == 0 || token[0] == '}') { + break; + } + + if ( Q_stricmp( token, "}" ) == 0 ) { + break; + } + + if (Q_stricmp(token, "gametypes") == 0) { + + if (GameType_Parse(&p, qfalse)) { + continue; + } else { + break; + } + } + + if (Q_stricmp(token, "joingametypes") == 0) { + + if (GameType_Parse(&p, qtrue)) { + continue; + } else { + break; + } + } + + if (Q_stricmp(token, "maps") == 0) { + // start a new menu + MapList_Parse(&p); + } + + } +} + +static void UI_Pause(qboolean b) { + if (b) { + // pause the game and set the ui keycatcher + trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + } else { + // unpause the game and clear the ui keycatcher + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + } +} + +static int UI_PlayCinematic(const char *name, float x, float y, float w, float h) { + return trap_CIN_PlayCinematic(name, x, y, w, h, (CIN_loop | CIN_silent)); +} + +static void UI_StopCinematic(int handle) { + if (handle >= 0) { + trap_CIN_StopCinematic(handle); + } else { + handle = abs(handle); + if (handle == UI_MAPCINEMATIC) { + 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) { + if (uiInfo.serverStatus.currentServerCinematic >= 0) { + trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic); + uiInfo.serverStatus.currentServerCinematic = -1; + } + } else if (handle == UI_CLANCINEMATIC) { + 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_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 UI_RunCinematicFrame(int handle) { + trap_CIN_RunCinematic(handle); +} + + +/* +================= +UI_LoadForceConfig_List +================= +Looks in the directory for force config files (.fcf) and loads the name in +*/ +void UI_LoadForceConfig_List( void ) +{ + int numfiles = 0; + char filelist[2048]; + char configname[128]; + char *fileptr = NULL; + int j = 0; + int filelen = 0; + qboolean lightSearch = qfalse; + + uiInfo.forceConfigCount = 0; + Com_sprintf( uiInfo.forceConfigNames[uiInfo.forceConfigCount], sizeof(uiInfo.forceConfigNames[uiInfo.forceConfigCount]), "Custom"); + uiInfo.forceConfigCount++; + //Always reserve index 0 as the "custom" config + +nextSearch: + if (lightSearch) + { //search light side folder + numfiles = trap_FS_GetFileList("forcecfg/light", "fcf", filelist, 2048 ); + uiInfo.forceConfigLightIndexBegin = uiInfo.forceConfigCount-1; + } + else + { //search dark side folder + numfiles = trap_FS_GetFileList("forcecfg/dark", "fcf", filelist, 2048 ); + uiInfo.forceConfigDarkIndexBegin = uiInfo.forceConfigCount-1; + } + + fileptr = filelist; + + for (j=0; j= MAX_Q3PLAYERMODELS) + { + return; + } + } + } + +} + +void UI_SiegeInit(void) +{ + //Load the player class types + BG_SiegeLoadClasses(g_UIClassDescriptions); + + if (!bgNumSiegeClasses) + { //We didn't find any?! + Com_Error(ERR_DROP, "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. + Com_Error(ERR_DROP, "Couldn't find any player teams for Siege"); + } +} + +/* +================= +UI_ParseColorData +================= +*/ +//static qboolean UI_ParseColorData(char* buf, playerSpeciesInfo_t &species) +static qboolean UI_ParseColorData(char* buf, playerSpeciesInfo_t *species,char* file) +{ + const char *token; + const char *p; + + p = buf; + COM_BeginParseSession(file); + 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]) ); + + 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 +} + +/* +================= +UI_BuildPlayerModel_List +================= +*/ +static void UI_BuildPlayerModel_List( qboolean inGameLoad ) +{ + int numdirs; + char dirlist[2048]; + char* dirptr; + int dirlen; + int i; + int j; + + + uiInfo.playerSpeciesCount = 0; + uiInfo.playerSpeciesIndex = 0; + memset(uiInfo.playerSpecies, 0, sizeof (uiInfo.playerSpecies) ); + + // iterate directory of all player models + numdirs = trap_FS_GetFileList("models/players", "/", dirlist, 2048 ); + dirptr = dirlist; + for (i=0; i 0 ) + { + trap_FS_FOpenFile(va("models/players/%s/%s",dirptr,fileptr), &f, FS_READ); + if (f) trap_FS_FCloseFile(f); + } + + filelen = strlen(fileptr); + COM_StripExtension(fileptr,skinname); + + if (bIsImageFile(dirptr, skinname)) + { //if it exists + if (strnicmp(skinname,"head_",5) == 0) + { + if (uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinHeadCount < MAX_PLAYERMODELS) + { + Q_strncpyz( + uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinHeadNames[uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinHeadCount++], + skinname, + sizeof(uiInfo.playerSpecies[0].SkinHeadNames[0]) + ); + iSkinParts |= 1<<0; + } + } else + if (strnicmp(skinname,"torso_",6) == 0) + { + if (uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinTorsoCount < MAX_PLAYERMODELS) + { + Q_strncpyz(uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinTorsoNames[uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinTorsoCount++], + skinname, + sizeof(uiInfo.playerSpecies[0].SkinTorsoNames[0]) + ); + iSkinParts |= 1<<1; + } + } else + if (strnicmp(skinname,"lower_",6) == 0) + { + if (uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinLegCount < MAX_PLAYERMODELS) + { + Q_strncpyz(uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinLegNames[uiInfo.playerSpecies[uiInfo.playerSpeciesCount].SkinLegCount++], + skinname, + sizeof(uiInfo.playerSpecies[0].SkinLegNames[0]) + ); + iSkinParts |= 1<<2; + } + } + + } + } + if (iSkinParts != 7) + { //didn't get a skin for each, then skip this model. + memset(&uiInfo.playerSpecies[uiInfo.playerSpeciesCount], 0, sizeof(uiInfo.playerSpecies[uiInfo.playerSpeciesCount]));//undo the colors + continue; + } + uiInfo.playerSpeciesCount++; + if (!inGameLoad && ui_PrecacheModels.integer) + { + int g2Model; + void *ghoul2 = 0; + Com_sprintf( fpath, sizeof( fpath ), "models/players/%s/model.glm", dirptr ); + g2Model = trap_G2API_InitGhoul2Model(&ghoul2, fpath, 0, 0, 0, 0, 0); + if (g2Model >= 0) + { +// trap_G2API_RemoveGhoul2Model( &ghoul2, 0 ); + trap_G2API_CleanGhoul2Models (&ghoul2); + } + } + if (uiInfo.playerSpeciesCount >= MAX_PLAYERMODELS) + { + return; + } + } + } + +} + +/* +================= +UI_Init +================= +*/ +void _UI_Init( qboolean inGameLoad ) { + const char *menuSet; + int start; + + //register this freakin thing now + vmCvar_t siegeTeamSwitch; + trap_Cvar_Register(&siegeTeamSwitch, "g_siegeTeamSwitch", "1", CVAR_SERVERINFO|CVAR_ARCHIVE); + + // Get the list of possible languages + uiInfo.languageCount = trap_SP_GetNumLanguages(); // this does a dir scan, so use carefully + + uiInfo.inGameLoad = inGameLoad; + + //initialize all these cvars to "0" + UI_SiegeSetCvarsForClass(NULL); + + UI_SiegeInit(); + + UI_UpdateForcePowers(); + + 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; + } + + + //UI_Load(); + uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + uiInfo.uiDC.setColor = &UI_SetColor; + uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic; + uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic; + uiInfo.uiDC.drawText = &Text_Paint; + uiInfo.uiDC.textWidth = &Text_Width; + uiInfo.uiDC.textHeight = &Text_Height; + uiInfo.uiDC.registerModel = &trap_R_RegisterModel; + uiInfo.uiDC.modelBounds = &trap_R_ModelBounds; + uiInfo.uiDC.fillRect = &UI_FillRect; + uiInfo.uiDC.drawRect = &_UI_DrawRect; + uiInfo.uiDC.drawSides = &_UI_DrawSides; + uiInfo.uiDC.drawTopBottom = &_UI_DrawTopBottom; + uiInfo.uiDC.clearScene = &trap_R_ClearScene; + uiInfo.uiDC.drawSides = &_UI_DrawSides; + uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + uiInfo.uiDC.renderScene = &trap_R_RenderScene; + uiInfo.uiDC.RegisterFont = &trap_R_RegisterFont; + uiInfo.uiDC.Font_StrLenPixels = trap_R_Font_StrLenPixels; + uiInfo.uiDC.Font_StrLenChars = trap_R_Font_StrLenChars; + uiInfo.uiDC.Font_HeightPixels = trap_R_Font_HeightPixels; + uiInfo.uiDC.Font_DrawString = trap_R_Font_DrawString; + uiInfo.uiDC.Language_IsAsian = trap_Language_IsAsian; + uiInfo.uiDC.Language_UsesSpaces = trap_Language_UsesSpaces; + uiInfo.uiDC.AnyLanguage_ReadCharFromString = trap_AnyLanguage_ReadCharFromString; + uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw; + uiInfo.uiDC.getValue = &UI_GetValue; + uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible; + uiInfo.uiDC.runScript = &UI_RunMenuScript; + uiInfo.uiDC.deferScript = &UI_DeferMenuScript; + uiInfo.uiDC.getTeamColor = &UI_GetTeamColor; + uiInfo.uiDC.setCVar = trap_Cvar_Set; + uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer; + uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue; + uiInfo.uiDC.drawTextWithCursor = &Text_PaintWithCursor; + uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound; + uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey; + uiInfo.uiDC.feederCount = &UI_FeederCount; + uiInfo.uiDC.feederItemImage = &UI_FeederItemImage; + uiInfo.uiDC.feederItemText = &UI_FeederItemText; + uiInfo.uiDC.feederSelection = &UI_FeederSelection; + uiInfo.uiDC.setBinding = &trap_Key_SetBinding; + uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf; + uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText; + uiInfo.uiDC.Error = &Com_Error; + uiInfo.uiDC.Print = &Com_Printf; + uiInfo.uiDC.Pause = &UI_Pause; + uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth; + uiInfo.uiDC.registerSound = &trap_S_RegisterSound; + uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + uiInfo.uiDC.playCinematic = &UI_PlayCinematic; + uiInfo.uiDC.stopCinematic = &UI_StopCinematic; + uiInfo.uiDC.drawCinematic = &UI_DrawCinematic; + uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame; + + Init_Display(&uiInfo.uiDC); + + UI_BuildPlayerModel_List(inGameLoad); + + String_Init(); + + uiInfo.uiDC.cursor = trap_R_RegisterShaderNoMip( "menu/art/3_cursor2" ); + uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip( "white" ); + + AssetCache(); + + start = trap_Milliseconds(); + + uiInfo.teamCount = 0; + uiInfo.characterCount = 0; + uiInfo.aliasCount = 0; + + UI_ParseGameInfo("ui/jamp/gameinfo.txt"); + + menuSet = UI_Cvar_VariableString("ui_menuFilesMP"); + if (menuSet == NULL || menuSet[0] == '\0') { + menuSet = "ui/jampmenus.txt"; + } + +#if 1 + if (inGameLoad) + { + UI_LoadMenus("ui/jampingame.txt", qtrue); + } + else if (!ui_bypassMainMenuLoad.integer) + { + UI_LoadMenus(menuSet, qtrue); + } +#else //this was adding quite a giant amount of time to the load time + UI_LoadMenus(menuSet, qtrue); + UI_LoadMenus("ui/jampingame.txt", qtrue); +#endif + + trap_Cvar_Register(NULL, "ui_name", UI_Cvar_VariableString("name"), CVAR_INTERNAL ); //get this now, jic the menus change again trying to setName before getName + + Menus_CloseAll(); + + trap_LAN_LoadCachedServers(); + UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum); + + UI_BuildQ3Model_List(); + UI_LoadBots(); + + UI_LoadForceConfig_List(); + + UI_InitForceShaders(); + + // sets defaults for ui temp cvars + uiInfo.effectsColor = /*gamecodetoui[*/(int)trap_Cvar_VariableValue("color1");//-1]; + uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair"); + trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1"); + trap_Cvar_Set("ui_mousePitchVeh", (trap_Cvar_VariableValue("m_pitchVeh") >= 0) ? "0" : "1"); + + uiInfo.serverStatus.currentServerCinematic = -1; + uiInfo.previewMovie = -1; + + trap_Cvar_Register(NULL, "debug_protocol", "", 0 ); + + trap_Cvar_Set("ui_actualNetGameType", va("%d", ui_netGameType.integer)); +} + +#ifdef _XBOX +#include "../namespace_begin.h" +extern void UpdateDemoTimer(); +#include "../namespace_end.h" +#endif + +/* +================= +UI_KeyEvent +================= +*/ +void _UI_KeyEvent( int key, qboolean down ) { + + if (Menu_Count() > 0) { + menuDef_t *menu = Menu_GetFocused(); + if (menu) { +//JLF +#ifdef _XBOX + + UpdateDemoTimer(); + +#endif + if (key == A_ESCAPE && down && !Menus_AnyFullScreenVisible()) { + Menus_CloseAll(); + } else { + Menu_HandleKey(menu, key, down ); + } + } else { + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + } + } + + //if ((s > 0) && (s != menu_null_sound)) { + // trap_S_StartLocalSound( s, CHAN_LOCAL_SOUND ); + //} +} + + +/* +================= +UI_MouseEvent +================= +*/ +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 (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); + } + +} + +void UI_LoadNonIngame() { + const char *menuSet = UI_Cvar_VariableString("ui_menuFilesMP"); + if (menuSet == NULL || menuSet[0] == '\0') { + menuSet = "ui/jampmenus.txt"; + } + UI_LoadMenus(menuSet, qfalse); + uiInfo.inGameLoad = qfalse; +} + +void _UI_SetActiveMenu( uiMenuCommand_t menu ) { + char buf[256]; + + // this should be the ONLY way the menu system is brought up + // enusure minumum menu data is cached + if (Menu_Count() > 0) { + vec3_t v; + v[0] = v[1] = v[2] = 0; + switch ( menu ) { + case UIMENU_NONE: + trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI ); + trap_Key_ClearStates(); + trap_Cvar_Set( "cl_paused", "0" ); + Menus_CloseAll(); + + return; + case UIMENU_MAIN: + { + qboolean active = qfalse; + + //trap_Cvar_Set( "sv_killserver", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + //trap_S_StartLocalSound( trap_S_RegisterSound("sound/misc/menu_background.wav", qfalse) , CHAN_LOCAL_SOUND ); + //trap_S_StartBackgroundTrack("sound/misc/menu_background.wav", NULL); + if (uiInfo.inGameLoad) + { +// UI_LoadNonIngame(); + } + + Menus_CloseAll(); + Menus_ActivateByName("main"); + trap_Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf)); + + if (buf[0]) + { + if (!ui_singlePlayerActive.integer) + { +#ifdef _XBOX + // Display Xbox popups after an ERR_DROP? + UI_xboxErrorPopup( XB_POPUP_COM_ERROR ); +#else + Menus_ActivateByName("error_popmenu"); +#endif + active = qtrue; + } + else + { + trap_Cvar_Set("com_errorMessage", ""); + } + } + return; + } + + case UIMENU_TEAM: + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_ActivateByName("team"); + return; + case UIMENU_POSTGAME: + //trap_Cvar_Set( "sv_killserver", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + if (uiInfo.inGameLoad) { +// UI_LoadNonIngame(); + } + Menus_CloseAll(); + Menus_ActivateByName("endofgame"); + //UI_ConfirmMenu( "Bad CD Key", NULL, NeedCDKeyAction ); + return; + case UIMENU_INGAME: + trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + UI_BuildPlayerList(); + Menus_CloseAll(); + Menus_ActivateByName("ingame"); + return; + case UIMENU_PLAYERCONFIG: + // trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + UI_BuildPlayerList(); + Menus_CloseAll(); + Menus_ActivateByName("ingame_player"); + UpdateForceUsed(); + return; + case UIMENU_PLAYERFORCE: + // trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + UI_BuildPlayerList(); + Menus_CloseAll(); + Menus_ActivateByName("ingame_playerforce"); + UpdateForceUsed(); + return; + case UIMENU_SIEGEMESSAGE: + // trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName("siege_popmenu"); + return; + case UIMENU_SIEGEOBJECTIVES: + // trap_Cvar_Set( "cl_paused", "1" ); + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName("ingame_siegeobjectives"); + return; + case UIMENU_VOICECHAT: + // trap_Cvar_Set( "cl_paused", "1" ); + // No chatin non-siege games. + + if (trap_Cvar_VariableValue( "g_gametype" ) < GT_TEAM) + { + return; + } + + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName("ingame_voicechat"); + return; + case UIMENU_CLOSEALL: + Menus_CloseAll(); + return; + case UIMENU_CLASSSEL: + trap_Key_SetCatcher( KEYCATCH_UI ); + Menus_CloseAll(); + Menus_ActivateByName("ingame_siegeclass"); + return; + } + } +} + +qboolean _UI_IsFullscreen( void ) { + return Menus_AnyFullScreenVisible(); +} + + + +static connstate_t lastConnState; +static char lastLoadingText[MAX_INFO_VALUE]; + +static void UI_ReadableSize ( char *buf, int bufsize, int value ) +{ + if (value > 1024*1024*1024 ) { // gigs + Com_sprintf( buf, bufsize, "%d", value / (1024*1024*1024) ); + Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d GB", + (value % (1024*1024*1024))*100 / (1024*1024*1024) ); + } else if (value > 1024*1024 ) { // megs + Com_sprintf( buf, bufsize, "%d", value / (1024*1024) ); + Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d MB", + (value % (1024*1024))*100 / (1024*1024) ); + } else if (value > 1024 ) { // kilos + Com_sprintf( buf, bufsize, "%d KB", value / 1024 ); + } else { // bytes + Com_sprintf( buf, bufsize, "%d bytes", value ); + } +} + +// Assumes time is in msec +static void UI_PrintTime ( char *buf, int bufsize, int time ) { + time /= 1000; // change to seconds + + if (time > 3600) { // in the hours range + Com_sprintf( buf, bufsize, "%d hr %2d min", time / 3600, (time % 3600) / 60 ); + } else if (time > 60) { // mins + Com_sprintf( buf, bufsize, "%2d min %2d sec", time / 60, time % 60 ); + } else { // secs + Com_sprintf( buf, bufsize, "%2d sec", time ); + } +} + +void Text_PaintCenter(float x, float y, float scale, vec4_t color, const char *text, float adjust, int iMenuFont) { + int len = Text_Width(text, scale, iMenuFont); + Text_Paint(x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, iMenuFont); +} + + +static void UI_DisplayDownloadInfo( const char *downloadName, float centerPoint, float yStart, float scale, int iMenuFont) { + char sDownLoading[256]; + char sEstimatedTimeLeft[256]; + char sTransferRate[256]; + char sOf[20]; + char sCopied[256]; + char sSec[20]; + // + int downloadSize, downloadCount, downloadTime; + char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64]; + int xferRate; + int leftWidth; + const char *s; + + vec4_t colorLtGreyAlpha = {0, 0, 0, .5}; + + UI_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, colorLtGreyAlpha ); + + s = GetCRDelineatedString("MENUS","DOWNLOAD_STUFF", 0); // "Downloading:" + strcpy(sDownLoading,s?s:""); + s = GetCRDelineatedString("MENUS","DOWNLOAD_STUFF", 1); // "Estimated time left:" + strcpy(sEstimatedTimeLeft,s?s:""); + s = GetCRDelineatedString("MENUS","DOWNLOAD_STUFF", 2); // "Transfer rate:" + strcpy(sTransferRate,s?s:""); + s = GetCRDelineatedString("MENUS","DOWNLOAD_STUFF", 3); // "of" + strcpy(sOf,s?s:""); + s = GetCRDelineatedString("MENUS","DOWNLOAD_STUFF", 4); // "copied" + strcpy(sCopied,s?s:""); + s = GetCRDelineatedString("MENUS","DOWNLOAD_STUFF", 5); // "sec." + strcpy(sSec,s?s:""); + + downloadSize = trap_Cvar_VariableValue( "cl_downloadSize" ); + downloadCount = trap_Cvar_VariableValue( "cl_downloadCount" ); + downloadTime = trap_Cvar_VariableValue( "cl_downloadTime" ); + + leftWidth = 320; + + UI_SetColor(colorWhite); + + Text_PaintCenter(centerPoint, yStart + 112, scale, colorWhite, sDownLoading, 0, iMenuFont); + Text_PaintCenter(centerPoint, yStart + 192, scale, colorWhite, sEstimatedTimeLeft, 0, iMenuFont); + Text_PaintCenter(centerPoint, yStart + 248, scale, colorWhite, sTransferRate, 0, iMenuFont); + + if (downloadSize > 0) { + s = va( "%s (%d%%)", downloadName, downloadCount * 100 / downloadSize ); + } else { + s = downloadName; + } + + Text_PaintCenter(centerPoint, yStart+136, scale, colorWhite, s, 0, iMenuFont); + + UI_ReadableSize( dlSizeBuf, sizeof dlSizeBuf, downloadCount ); + UI_ReadableSize( totalSizeBuf, sizeof totalSizeBuf, downloadSize ); + + if (downloadCount < 4096 || !downloadTime) { + Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0, iMenuFont); + Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s %s %s %s)", dlSizeBuf, sOf, totalSizeBuf, sCopied), 0, iMenuFont); + } else { + if ((uiInfo.uiDC.realTime - downloadTime) / 1000) { + xferRate = downloadCount / ((uiInfo.uiDC.realTime - downloadTime) / 1000); + } else { + xferRate = 0; + } + UI_ReadableSize( xferRateBuf, sizeof xferRateBuf, xferRate ); + + // Extrapolate estimated completion time + if (downloadSize && xferRate) { + int n = downloadSize / xferRate; // estimated time for entire d/l in secs + + // We do it in K (/1024) because we'd overflow around 4MB + UI_PrintTime ( dlTimeBuf, sizeof dlTimeBuf, + (n - (((downloadCount/1024) * n) / (downloadSize/1024))) * 1000); + + Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, dlTimeBuf, 0, iMenuFont); + Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s %s %s %s)", dlSizeBuf, sOf, totalSizeBuf, sCopied), 0, iMenuFont); + } else { + Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0, iMenuFont); + if (downloadSize) { + Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s %s %s %s)", dlSizeBuf, sOf, totalSizeBuf, sCopied), 0, iMenuFont); + } else { + Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s %s)", dlSizeBuf, sCopied), 0, iMenuFont); + } + } + + if (xferRate) { + Text_PaintCenter(leftWidth, yStart+272, scale, colorWhite, va("%s/%s", xferRateBuf,sSec), 0, iMenuFont); + } + } +} + +/* +======================== +UI_DrawConnectScreen + +This will also be overlaid on the cgame info screen during loading +to prevent it from blinking away too rapidly on local or lan games. +======================== +*/ +void UI_DrawConnectScreen( qboolean overlay ) { + const char *s; + uiClientState_t cstate; + char info[MAX_INFO_VALUE]; + char text[256]; + float centerPoint, yStart, scale; + + char sStringEdTemp[256]; + + menuDef_t *menu = Menus_FindByName("Connect"); + + + if ( !overlay && menu ) { + Menu_Paint(menu, qtrue); + } + + if (!overlay) { + centerPoint = 320; + yStart = 130; + scale = 1.0f; // -ste + } else { + centerPoint = 320; + yStart = 32; + scale = 1.0f; // -ste + return; + } + + // see what information we should display + trap_GetClientState( &cstate ); + + + info[0] = '\0'; + if( trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ) ) { + trap_SP_GetStringTextString("MENUS_LOADING_MAPNAME", sStringEdTemp, sizeof(sStringEdTemp)); + Text_PaintCenter(centerPoint, yStart, scale, colorWhite, va( /*"Loading %s"*/sStringEdTemp, Info_ValueForKey( info, "mapname" )), 0, FONT_MEDIUM); + } + + if (!Q_stricmp(cstate.servername,"localhost")) { + trap_SP_GetStringTextString("MENUS_STARTING_UP", sStringEdTemp, sizeof(sStringEdTemp)); + Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite, sStringEdTemp, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM); + } else { + trap_SP_GetStringTextString("MENUS_CONNECTING_TO", sStringEdTemp, sizeof(sStringEdTemp)); + strcpy(text, va(/*"Connecting to %s"*/sStringEdTemp, cstate.servername)); + Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite,text , ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM); + } + + //UI_DrawProportionalString( 320, 96, "Press Esc to abort", UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, menu_text_color ); + + // display global MOTD at bottom + Text_PaintCenter(centerPoint, 425, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0, FONT_MEDIUM); + // print any server info (server full, bad version, etc) + if ( cstate.connState < CA_CONNECTED ) { + Text_PaintCenter(centerPoint, yStart + 176, scale, colorWhite, cstate.messageString, 0, FONT_MEDIUM); + } + + if ( lastConnState > cstate.connState ) { + lastLoadingText[0] = '\0'; + } + lastConnState = cstate.connState; + + switch ( cstate.connState ) { + case CA_CONNECTING: + { + trap_SP_GetStringTextString("MENUS_AWAITING_CONNECTION", sStringEdTemp, sizeof(sStringEdTemp)); + s = va(/*"Awaiting connection...%i"*/sStringEdTemp, cstate.connectPacketCount); + } + break; + case CA_CHALLENGING: + { + trap_SP_GetStringTextString("MENUS_AWAITING_CHALLENGE", sStringEdTemp, sizeof(sStringEdTemp)); + s = va(/*"Awaiting challenge...%i"*/sStringEdTemp, cstate.connectPacketCount); + } + break; + case CA_CONNECTED: { + char downloadName[MAX_INFO_VALUE]; + + trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof(downloadName) ); + if (*downloadName) { + UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, scale, FONT_MEDIUM ); + return; + } + } + trap_SP_GetStringTextString("MENUS_AWAITING_GAMESTATE", sStringEdTemp, sizeof(sStringEdTemp)); + s = /*"Awaiting gamestate..."*/sStringEdTemp; + break; + case CA_LOADING: + return; + case CA_PRIMED: + return; + default: + return; + } + + if (Q_stricmp(cstate.servername,"localhost")) { + Text_PaintCenter(centerPoint, yStart + 80, scale, colorWhite, s, 0, FONT_MEDIUM); + } + // password required / connection rejected information goes here +} + + +/* +================ +cvars +================ +*/ + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +vmCvar_t ui_ffa_fraglimit; +vmCvar_t ui_ffa_timelimit; + +vmCvar_t ui_tourney_fraglimit; +vmCvar_t ui_tourney_timelimit; + +vmCvar_t ui_selectedModelIndex; +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_team_fraglimit; +vmCvar_t ui_team_timelimit; +vmCvar_t ui_team_friendly; + +vmCvar_t ui_ctf_capturelimit; +vmCvar_t ui_ctf_timelimit; +vmCvar_t ui_ctf_friendly; + +vmCvar_t ui_arenasFile; +vmCvar_t ui_botsFile; +vmCvar_t ui_spSkill; + +vmCvar_t ui_browserMaster; +vmCvar_t ui_browserGameType; +vmCvar_t ui_browserSortKey; +vmCvar_t ui_browserShowFull; +vmCvar_t ui_browserShowEmpty; + +vmCvar_t ui_drawCrosshair; +vmCvar_t ui_drawCrosshairNames; +vmCvar_t ui_marks; + +vmCvar_t ui_server1; +vmCvar_t ui_server2; +vmCvar_t ui_server3; +vmCvar_t ui_server4; +vmCvar_t ui_server5; +vmCvar_t ui_server6; +vmCvar_t ui_server7; +vmCvar_t ui_server8; +vmCvar_t ui_server9; +vmCvar_t ui_server10; +vmCvar_t ui_server11; +vmCvar_t ui_server12; +vmCvar_t ui_server13; +vmCvar_t ui_server14; +vmCvar_t ui_server15; +vmCvar_t ui_server16; + +vmCvar_t ui_redteam; +vmCvar_t ui_redteam1; +vmCvar_t ui_redteam2; +vmCvar_t ui_redteam3; +vmCvar_t ui_redteam4; +vmCvar_t ui_redteam5; +vmCvar_t ui_redteam6; +vmCvar_t ui_redteam7; +vmCvar_t ui_redteam8; +vmCvar_t ui_blueteam; +vmCvar_t ui_blueteam1; +vmCvar_t ui_blueteam2; +vmCvar_t ui_blueteam3; +vmCvar_t ui_blueteam4; +vmCvar_t ui_blueteam5; +vmCvar_t ui_blueteam6; +vmCvar_t ui_blueteam7; +vmCvar_t ui_blueteam8; +vmCvar_t ui_teamName; +vmCvar_t ui_dedicated; +vmCvar_t ui_gameType; +vmCvar_t ui_netGameType; +vmCvar_t ui_actualNetGameType; +vmCvar_t ui_joinGameType; +#ifdef _XBOX +vmCvar_t ui_optiGameType; +vmCvar_t ui_optiCurrentMap; +vmCvar_t ui_optiMinPlayers; +vmCvar_t ui_optiMaxPlayers; +vmCvar_t ui_optiFriendlyFire; +vmCvar_t ui_optiJediMastery; +vmCvar_t ui_optiSaberOnly; +#endif +vmCvar_t ui_netSource; +vmCvar_t ui_serverFilterType; +vmCvar_t ui_opponentName; +vmCvar_t ui_menuFiles; +vmCvar_t ui_currentMap; +vmCvar_t ui_currentNetMap; +vmCvar_t ui_mapIndex; +vmCvar_t ui_currentOpponent; +vmCvar_t ui_selectedPlayer; +vmCvar_t ui_selectedPlayerName; +vmCvar_t ui_lastServerRefresh_0; +vmCvar_t ui_lastServerRefresh_1; +vmCvar_t ui_lastServerRefresh_2; +vmCvar_t ui_lastServerRefresh_3; +vmCvar_t ui_singlePlayerActive; +vmCvar_t ui_scoreAccuracy; +vmCvar_t ui_scoreImpressives; +vmCvar_t ui_scoreExcellents; +vmCvar_t ui_scoreCaptures; +vmCvar_t ui_scoreDefends; +vmCvar_t ui_scoreAssists; +vmCvar_t ui_scoreGauntlets; +vmCvar_t ui_scoreScore; +vmCvar_t ui_scorePerfect; +vmCvar_t ui_scoreTeam; +vmCvar_t ui_scoreBase; +vmCvar_t ui_scoreTimeBonus; +vmCvar_t ui_scoreSkillBonus; +vmCvar_t ui_scoreShutoutBonus; +vmCvar_t ui_scoreTime; +vmCvar_t ui_captureLimit; +vmCvar_t ui_fragLimit; +vmCvar_t ui_findPlayer; +vmCvar_t ui_hudFiles; +vmCvar_t ui_recordSPDemo; +vmCvar_t ui_realCaptureLimit; +vmCvar_t ui_realWarmUp; +vmCvar_t ui_serverStatusTimeOut; +vmCvar_t se_language; + +vmCvar_t ui_bypassMainMenuLoad; + +// bk001129 - made static to avoid aliasing +static cvarTable_t cvarTable[] = { + { &ui_ffa_fraglimit, "ui_ffa_fraglimit", "20", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_ffa_timelimit, "ui_ffa_timelimit", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + + { &ui_tourney_fraglimit, "ui_tourney_fraglimit", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_tourney_timelimit, "ui_tourney_timelimit", "15", CVAR_ARCHIVE|CVAR_INTERNAL }, + + { &ui_selectedModelIndex, "ui_selectedModelIndex", "16", CVAR_ARCHIVE|CVAR_INTERNAL }, + + { &ui_char_model, "ui_char_model", "jedi_tf",CVAR_ROM|CVAR_INTERNAL}, + { &ui_char_skin_head, "ui_char_skin_head", "head_a1",CVAR_ROM|CVAR_INTERNAL}, + { &ui_char_skin_torso, "ui_char_skin_torso", "torso_a1",CVAR_ROM|CVAR_INTERNAL}, + { &ui_char_skin_legs, "ui_char_skin_legs", "lower_a1",CVAR_ROM|CVAR_INTERNAL}, + + { &ui_char_anim, "ui_char_anim", "BOTH_WALK1",CVAR_ROM|CVAR_INTERNAL}, + + { &ui_saber_type, "ui_saber_type", "single",CVAR_ROM|CVAR_INTERNAL}, + { &ui_saber, "ui_saber", "single_1",CVAR_ROM|CVAR_INTERNAL}, + { &ui_saber2, "ui_saber2", "none",CVAR_ROM|CVAR_INTERNAL}, + { &ui_saber_color, "ui_saber_color", "yellow",CVAR_ROM|CVAR_INTERNAL}, + { &ui_saber2_color, "ui_saber2_color", "yellow",CVAR_ROM|CVAR_INTERNAL}, + + { &ui_char_color_red, "ui_char_color_red", "255", CVAR_ROM|CVAR_INTERNAL}, + { &ui_char_color_green, "ui_char_color_green", "255", CVAR_ROM|CVAR_INTERNAL}, + { &ui_char_color_blue, "ui_char_color_blue", "255", CVAR_ROM|CVAR_INTERNAL}, + + { &ui_PrecacheModels, "ui_PrecacheModels", "0", CVAR_ARCHIVE}, + + { &ui_team_fraglimit, "ui_team_fraglimit", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_team_timelimit, "ui_team_timelimit", "20", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_team_friendly, "ui_team_friendly", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + + { &ui_ctf_capturelimit, "ui_ctf_capturelimit", "8", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_ctf_timelimit, "ui_ctf_timelimit", "30", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_ctf_friendly, "ui_ctf_friendly", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + + { &ui_botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM }, + { &ui_spSkill, "g_spSkill", "2", CVAR_ARCHIVE|CVAR_INTERNAL }, + + { &ui_browserMaster, "ui_browserMaster", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_browserGameType, "ui_browserGameType", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_browserSortKey, "ui_browserSortKey", "4", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + + { &ui_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_marks, "cg_marks", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + + { &ui_server1, "server1", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server2, "server2", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server3, "server3", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server4, "server4", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server5, "server5", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server6, "server6", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server7, "server7", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server8, "server8", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server9, "server9", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server10, "server10", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server11, "server11", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server12, "server12", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server13, "server13", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server14, "server14", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server15, "server15", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_server16, "server16", "", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_debug, "ui_debug", "0", CVAR_TEMP|CVAR_INTERNAL }, + { &ui_initialized, "ui_initialized", "0", CVAR_TEMP|CVAR_INTERNAL }, + { &ui_teamName, "ui_teamName", "Empire", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_opponentName, "ui_opponentName", "Rebellion", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_rankChange, "ui_rankChange", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_freeSaber, "ui_freeSaber", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_forcePowerDisable, "ui_forcePowerDisable", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_redteam, "ui_redteam", "Empire", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam, "ui_blueteam", "Rebellion", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_gameType, "ui_gametype", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_joinGameType, "ui_joinGametype", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_netGameType, "ui_netGametype", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_actualNetGameType, "ui_actualNetGametype", "3", CVAR_ARCHIVE|CVAR_INTERNAL }, +#ifdef _XBOX + { &ui_optiGameType, "ui_optiGameType", "0", CVAR_ARCHIVE }, + { &ui_optiCurrentMap, "ui_optiCurrentMap", "0", CVAR_ARCHIVE }, + { &ui_optiMinPlayers, "ui_optiMinPlayers", "0", CVAR_ARCHIVE }, + { &ui_optiMaxPlayers, "ui_optiMaxPlayers", "0", CVAR_ARCHIVE }, + { &ui_optiFriendlyFire, "ui_optiFriendlyFire", "0", CVAR_ARCHIVE }, + { &ui_optiJediMastery, "ui_optiJediMastery", "0", CVAR_ARCHIVE }, + { &ui_optiSaberOnly, "ui_optiSaberOnly", "0", CVAR_ARCHIVE }, +#endif + { &ui_redteam1, "ui_redteam1", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, //rww - these used to all default to 0 (closed).. I changed them to 1 (human) + { &ui_redteam2, "ui_redteam2", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_redteam3, "ui_redteam3", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_redteam4, "ui_redteam4", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_redteam5, "ui_redteam5", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_redteam6, "ui_redteam6", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_redteam7, "ui_redteam7", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_redteam8, "ui_redteam8", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam1, "ui_blueteam1", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam2, "ui_blueteam2", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam3, "ui_blueteam3", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam4, "ui_blueteam4", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam5, "ui_blueteam5", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam6, "ui_blueteam6", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam7, "ui_blueteam7", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_blueteam8, "ui_blueteam8", "1", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_menuFiles, "ui_menuFilesMP", "ui/jampmenus.txt", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_currentMap, "ui_currentMap", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_currentNetMap, "ui_currentNetMap", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_mapIndex, "ui_mapIndex", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_currentOpponent, "ui_currentOpponent", "0", CVAR_ARCHIVE|CVAR_INTERNAL }, + { &ui_selectedPlayer, "cg_selectedPlayer", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_selectedPlayerName, "cg_selectedPlayerName", "", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0", "", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1", "", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2", "", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3", "", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_INTERNAL}, + { &ui_scoreAccuracy, "ui_scoreAccuracy", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreImpressives, "ui_scoreImpressives", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreExcellents, "ui_scoreExcellents", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreCaptures, "ui_scoreCaptures", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreDefends, "ui_scoreDefends", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreAssists, "ui_scoreAssists", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreGauntlets, "ui_scoreGauntlets", "0",CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreScore, "ui_scoreScore", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scorePerfect, "ui_scorePerfect", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreTeam, "ui_scoreTeam", "0 to 0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreBase, "ui_scoreBase", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreTime, "ui_scoreTime", "00:00", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreTimeBonus, "ui_scoreTimeBonus", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreSkillBonus, "ui_scoreSkillBonus", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_scoreShutoutBonus, "ui_scoreShutoutBonus", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_fragLimit, "ui_fragLimit", "10", CVAR_INTERNAL}, + { &ui_captureLimit, "ui_captureLimit", "5", CVAR_INTERNAL}, + { &ui_findPlayer, "ui_findPlayer", "Kyle", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_realWarmUp, "g_warmup", "20", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &ui_realCaptureLimit, "capturelimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE| CVAR_INTERNAL | CVAR_NORESTART}, + { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE|CVAR_INTERNAL}, + { &se_language, "se_language","english", CVAR_ARCHIVE | CVAR_NORESTART}, //text (string ed) + + { &ui_bypassMainMenuLoad, "ui_bypassMainMenuLoad", "0", CVAR_INTERNAL }, +//JLFCALLOUT +#ifdef _XBOX + { &ui_hideAcallout, "ui_hideAcallout", "", 0}, + { &ui_hideBcallout, "ui_hideBcallout", "", 0}, + { &ui_hideXcallout, "ui_hideXcallout", "", 0}, +#endif +//END JLFCALLOUT +}; + +// bk001129 - made static to avoid aliasing +static int cvarTableSize = sizeof(cvarTable) / sizeof(cvarTable[0]); + + +/* +================= +UI_RegisterCvars +================= +*/ +void UI_RegisterCvars( void ) { + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags ); + } +} + +/* +================= +UI_UpdateCvars +================= +*/ +void UI_UpdateCvars( void ) { + int i; + cvarTable_t *cv; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Update( cv->vmCvar ); + } +} + + +/* +================= +ArenaServers_StopRefresh +================= +*/ +static void UI_StopServerRefresh( void ) +{ + int count; + + if (!uiInfo.serverStatus.refreshActive) { + // not currently refreshing + return; + } + uiInfo.serverStatus.refreshActive = qfalse; + Com_Printf("%d servers listed in browser with %d players.\n", + uiInfo.serverStatus.numDisplayServers, + uiInfo.serverStatus.numPlayersOnServers); + count = trap_LAN_GetServerCount(ui_netSource.integer); + if (count - uiInfo.serverStatus.numDisplayServers > 0) { + Com_Printf("%d servers not listed due to packet loss or pings higher than %d\n", + count - uiInfo.serverStatus.numDisplayServers, + (int) trap_Cvar_VariableValue("cl_maxPing")); + } + +} + + +/* +================= +UI_DoServerRefresh +================= +*/ +static void UI_DoServerRefresh( void ) +{ + qboolean wait = qfalse; + + if (!uiInfo.serverStatus.refreshActive) { + return; + } + if (ui_netSource.integer != AS_FAVORITES) { + if (ui_netSource.integer == AS_LOCAL) { + if (!trap_LAN_GetServerCount(ui_netSource.integer)) { + wait = qtrue; + } + } else { + if (trap_LAN_GetServerCount(ui_netSource.integer) < 0) { + wait = qtrue; + } + } + } + + if (uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime) { + if (wait) { + return; + } + } + + // if still trying to retrieve pings + if (trap_LAN_UpdateVisiblePings(ui_netSource.integer)) { + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + } else if (!wait) { + // get the last servers in the list + UI_BuildServerDisplayList(2); + // stop the refresh + UI_StopServerRefresh(); + } + // + UI_BuildServerDisplayList(qfalse); +} + +/* +================= +UI_StartServerRefresh +================= +*/ +static void UI_StartServerRefresh(qboolean full) +{ + int i; + char *ptr; + + qtime_t q; + trap_RealTime(&q); + trap_Cvar_Set( va("ui_lastServerRefresh_%i", ui_netSource.integer), va("%s-%i, %i @ %i:%2i", GetMonthAbbrevString(q.tm_mon),q.tm_mday, 1900+q.tm_year,q.tm_hour,q.tm_min)); + + if (!full) { + UI_UpdatePendingPings(); + return; + } + + uiInfo.serverStatus.refreshActive = qtrue; + uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000; + // clear number of displayed servers + uiInfo.serverStatus.numDisplayServers = 0; + uiInfo.serverStatus.numPlayersOnServers = 0; + // mark all servers as visible so we store ping updates for them + trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue); + // reset all the pings + trap_LAN_ResetPings(ui_netSource.integer); + // + if( ui_netSource.integer == AS_LOCAL ) { + trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" ); + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000; + return; + } + + uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000; +#ifndef _XBOX // Optimatch is handled elsewhere + if( ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER ) { + if( ui_netSource.integer == AS_GLOBAL ) { + i = 0; + } + else { + i = 1; + } + + ptr = UI_Cvar_VariableString("debug_protocol"); + if (strlen(ptr)) { + trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %s\n", i, ptr)); + } + else { + trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %d\n", i, (int)trap_Cvar_VariableValue( "protocol" ) ) ); + } + } +#endif +} + diff --git a/codemp/ui/ui_players.c b/codemp/ui/ui_players.c new file mode 100644 index 0000000..5111460 --- /dev/null +++ b/codemp/ui/ui_players.c @@ -0,0 +1,1338 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// ui_players.c + +#include "ui_local.h" + + +#define UI_TIMER_GESTURE 2300 +#define UI_TIMER_JUMP 1000 +#define UI_TIMER_LAND 130 +#define UI_TIMER_WEAPON_SWITCH 300 +#define UI_TIMER_ATTACK 500 +#define UI_TIMER_MUZZLE_FLASH 20 +#define UI_TIMER_WEAPON_DELAY 250 + +#define JUMP_HEIGHT 56 + +#define SWINGSPEED 0.3f + +#define SPIN_SPEED 0.9f +#define COAST_TIME 1000 + + +static int dp_realtime; +static float jumpHeight; +sfxHandle_t weaponChangeSound; + + +/* +=============== +UI_PlayerInfo_SetWeapon +=============== +*/ +static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum ) { + gitem_t * item; + char path[MAX_QPATH]; + + pi->currentWeapon = weaponNum; +tryagain: + pi->realWeapon = weaponNum; + pi->weaponModel = 0; + pi->barrelModel = 0; + pi->flashModel = 0; + + if ( weaponNum == WP_NONE ) { + return; + } + + for ( item = bg_itemlist + 1; item->classname ; item++ ) { + if ( item->giType != IT_WEAPON ) { + continue; + } + if ( item->giTag == weaponNum ) { + break; + } + } + + if ( item->classname ) { + pi->weaponModel = trap_R_RegisterModel( item->world_model[0] ); + } + + if( pi->weaponModel == 0 ) { + if( weaponNum == WP_BRYAR_PISTOL ) { + weaponNum = WP_NONE; + goto tryagain; + } + weaponNum = WP_BRYAR_PISTOL; + goto tryagain; + } +/* + if ( weaponNum == WP_MACHINEGUN || weaponNum == WP_BFG ) { + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_barrel.md3" ); + pi->barrelModel = trap_R_RegisterModel( path ); + } +*/ + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_flash.md3" ); + pi->flashModel = trap_R_RegisterModel( path ); + + switch( weaponNum ) { + case WP_STUN_BATON: + case WP_SABER: + MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 ); + break; + + case WP_BRYAR_PISTOL: + 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: + MAKERGB( pi->flashDlightColor, 1, 1, 0 ); + break; + + default: + MAKERGB( pi->flashDlightColor, 1, 1, 1 ); + break; + } +} + + +/* +=============== +UI_ForceLegsAnim +=============== +*/ +static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) { + pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + + if ( anim == BOTH_JUMP1 ) { + pi->legsAnimationTimer = UI_TIMER_JUMP; + } +} + + +/* +=============== +UI_SetLegsAnim +=============== +*/ +static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) { + if ( pi->pendingLegsAnim ) { + anim = pi->pendingLegsAnim; + pi->pendingLegsAnim = 0; + } + UI_ForceLegsAnim( pi, anim ); +} + + +/* +=============== +UI_ForceTorsoAnim +=============== +*/ +static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) { + pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; + + if ( anim == BOTH_GESTURE1 ) { + pi->torsoAnimationTimer = UI_TIMER_GESTURE; + } + + if ( anim == BOTH_ATTACK3 || anim == BOTH_A1_T__B_ ) { + pi->torsoAnimationTimer = UI_TIMER_ATTACK; + } +} + + +/* +=============== +UI_SetTorsoAnim +=============== +*/ +static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) { + if ( pi->pendingTorsoAnim ) { + anim = pi->pendingTorsoAnim; + pi->pendingTorsoAnim = 0; + } + + UI_ForceTorsoAnim( pi, anim ); +} + + +/* +=============== +UI_TorsoSequencing +=============== +*/ +static void UI_TorsoSequencing( playerInfo_t *pi ) { + int currentAnim; + + currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; + + if ( pi->weapon != pi->currentWeapon ) { + if ( currentAnim != TORSO_DROPWEAP1 ) { + pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; + UI_ForceTorsoAnim( pi, TORSO_DROPWEAP1 ); + } + } + + if ( pi->torsoAnimationTimer > 0 ) { + return; + } + + if( currentAnim == BOTH_GESTURE1 ) { + UI_SetTorsoAnim( pi, TORSO_WEAPONREADY3 ); + return; + } + + if( currentAnim == BOTH_ATTACK3 || currentAnim == BOTH_A1_T__B_ ) { + UI_SetTorsoAnim( pi, TORSO_WEAPONREADY3 ); + return; + } + + if ( currentAnim == TORSO_DROPWEAP1 ) { + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); + pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; + UI_ForceTorsoAnim( pi, TORSO_RAISEWEAP1 ); + return; + } + + if ( currentAnim == TORSO_RAISEWEAP1 ) { + UI_SetTorsoAnim( pi, TORSO_WEAPONREADY3 ); + return; + } +} + + +/* +=============== +UI_LegsSequencing +=============== +*/ +static void UI_LegsSequencing( playerInfo_t *pi ) { + int currentAnim; + + currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; + + if ( pi->legsAnimationTimer > 0 ) { + if ( currentAnim == BOTH_JUMP1 ) { + jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP ); + } + return; + } + + if ( currentAnim == BOTH_JUMP1 ) { + UI_ForceLegsAnim( pi, BOTH_LAND1 ); + pi->legsAnimationTimer = UI_TIMER_LAND; + jumpHeight = 0; + return; + } + + if ( currentAnim == BOTH_LAND1 ) { + UI_SetLegsAnim( pi, TORSO_WEAPONREADY3 ); + return; + } +} + + +/* +====================== +UI_PositionEntityOnTag +====================== +*/ +static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + clipHandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + + // lerp the tag + trap_CM_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 ); + } + + // cast away const because of compiler problems + MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis ); + entity->backlerp = parent->backlerp; +} + + +/* +====================== +UI_PositionRotatedEntityOnTag +====================== +*/ +static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + clipHandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + vec3_t tempAxis[3]; + + // lerp the tag + trap_CM_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 ); + } + + // cast away const because of compiler problems + MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis ); + MatrixMultiply( lerped.axis, tempAxis, entity->axis ); +} + + +/* +=============== +UI_SetLerpFrameAnimation +=============== +*/ +static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= MAX_ANIMATIONS ) { + trap_Error( va("Bad animation number: %i", newAnimation) ); + } + + anim = &ci->animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; +} + + +/* +=============== +UI_RunLerpFrame +=============== +*/ +static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + int f; + animation_t *anim; + + // see if the animation sequence is switching + if ( newAnimation != lf->animationNumber || !lf->animation ) { + UI_SetLerpFrameAnimation( ci, lf, newAnimation ); + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( dp_realtime >= lf->frameTime ) { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if ( dp_realtime < lf->animationTime ) { + lf->frameTime = lf->animationTime; // initial lerp + } else { + lf->frameTime = lf->oldFrameTime + anim->frameLerp; + } + f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + if ( f >= anim->numFrames ) { + f -= anim->numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = anim->numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = dp_realtime; + } + } + lf->frame = anim->firstFrame + f; + if ( dp_realtime > lf->frameTime ) { + lf->frameTime = dp_realtime; + } + } + + if ( lf->frameTime > dp_realtime + 200 ) { + lf->frameTime = dp_realtime; + } + + if ( lf->oldFrameTime > dp_realtime ) { + lf->oldFrameTime = dp_realtime; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} + + +/* +=============== +UI_PlayerAnimation +=============== +*/ +static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) { + + // legs animation + pi->legsAnimationTimer -= uiInfo.uiDC.frameTime; + if ( pi->legsAnimationTimer < 0 ) { + pi->legsAnimationTimer = 0; + } + + UI_LegsSequencing( pi ); + + if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == TORSO_WEAPONREADY3 ) { + UI_RunLerpFrame( pi, &pi->legs, TORSO_WEAPONREADY3 ); + } else { + UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim ); + } + *legsOld = pi->legs.oldFrame; + *legs = pi->legs.frame; + *legsBackLerp = pi->legs.backlerp; + + // torso animation + pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime; + if ( pi->torsoAnimationTimer < 0 ) { + pi->torsoAnimationTimer = 0; + } + + UI_TorsoSequencing( pi ); + + UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim ); + *torsoOld = pi->torso.oldFrame; + *torso = pi->torso.frame; + *torsoBackLerp = pi->torso.backlerp; +} + + +/* +================== +UI_SwingAngles +================== +*/ +static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging ) { + 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; + } + + // 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 = uiInfo.uiDC.frameTime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = uiInfo.uiDC.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) ); + } +} + + +/* +====================== +UI_MovedirAdjustment +====================== +*/ +static float UI_MovedirAdjustment( playerInfo_t *pi ) { + vec3_t relativeAngles; + vec3_t moveVector; + + VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles ); + AngleVectors( relativeAngles, moveVector, NULL, NULL ); + if ( Q_fabs( moveVector[0] ) < 0.01 ) { + moveVector[0] = 0.0; + } + if ( Q_fabs( moveVector[1] ) < 0.01 ) { + moveVector[1] = 0.0; + } + + if ( moveVector[1] == 0 && moveVector[0] > 0 ) { + return 0; + } + if ( moveVector[1] < 0 && moveVector[0] > 0 ) { + return 22; + } + if ( moveVector[1] < 0 && moveVector[0] == 0 ) { + return 45; + } + if ( moveVector[1] < 0 && moveVector[0] < 0 ) { + return -22; + } + if ( moveVector[1] == 0 && moveVector[0] < 0 ) { + return 0; + } + if ( moveVector[1] > 0 && moveVector[0] < 0 ) { + return 22; + } + if ( moveVector[1] > 0 && moveVector[0] == 0 ) { + return -45; + } + + return -22; +} + + +/* +=============== +UI_PlayerAngles +=============== +*/ +static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { + vec3_t legsAngles, torsoAngles, headAngles; + float dest; + float adjust; + + VectorCopy( pi->viewAngles, headAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + + // --------- yaw ------------- + + // allow yaw to drift a bit + if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != TORSO_WEAPONREADY3 + || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_WEAPONREADY3 ) { + // if not standing still, always point all in the same direction + pi->torso.yawing = qtrue; // always center + pi->torso.pitching = qtrue; // always center + pi->legs.yawing = qtrue; // always center + } + + // adjust legs for movement dir + adjust = UI_MovedirAdjustment( pi ); + legsAngles[YAW] = headAngles[YAW] + adjust; + torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust; + + + // torso + UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing ); + UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing ); + + torsoAngles[YAW] = pi->torso.yawAngle; + legsAngles[YAW] = pi->legs.yawAngle; + + // --------- pitch ------------- + + // 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; + } + UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching ); + torsoAngles[PITCH] = pi->torso.pitchAngle; + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( torsoAngles, torso ); + AnglesToAxis( headAngles, head ); +} + + +/* +=============== +UI_PlayerFloatSprite +=============== +*/ +static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) { + refEntity_t ent; + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( origin, ent.origin ); + ent.origin[2] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = 0; + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +====================== +UI_MachinegunSpinAngle +====================== +*/ +float UI_MachinegunSpinAngle( playerInfo_t *pi ) { + int delta; + float angle; + float speed; + int torsoAnim; + + delta = dp_realtime - pi->barrelTime; + if ( pi->barrelSpinning ) { + angle = pi->barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = pi->barrelAngle + delta * speed; + } + + torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; + if( torsoAnim == BOTH_A1_T__B_ ) { + torsoAnim = BOTH_ATTACK3; + } + if ( pi->barrelSpinning == !(torsoAnim == BOTH_ATTACK3) ) { + pi->barrelTime = dp_realtime; + pi->barrelAngle = AngleMod( angle ); + pi->barrelSpinning = !!(torsoAnim == BOTH_ATTACK3); + } + + return angle; +} + + +/* +=============== +UI_DrawPlayer +=============== +*/ +void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) { + refdef_t refdef; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + refEntity_t gun; +// refEntity_t barrel; + refEntity_t flash; + vec3_t origin; + int renderfx; + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + float len; + float xx; + + if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) { + return; + } + + // this allows the ui to cache the player model on the main menu + if (w == 0 || h == 0) { + return; + } + + dp_realtime = time; + + if ( pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer ) { + pi->weapon = pi->pendingWeapon; + pi->lastWeapon = pi->pendingWeapon; + pi->pendingWeapon = -1; + pi->weaponTimer = 0; + if( pi->currentWeapon != pi->weapon ) { + trap_S_StartLocalSound( weaponChangeSound, CHAN_LOCAL ); + } + } + + y -= jumpHeight; + + memset( &refdef, 0, sizeof( refdef ) ); + memset( &legs, 0, sizeof(legs) ); + memset( &torso, 0, sizeof(torso) ); + memset( &head, 0, sizeof(head) ); + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f); + xx = refdef.width / tan( refdef.fov_x / 360 * M_PI ); + refdef.fov_y = atan2( refdef.height, xx ); + refdef.fov_y *= ( 360 / (float)M_PI ); + + // calculate distance so the player nearly fills the box + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + + refdef.time = dp_realtime; + + trap_R_ClearScene(); + + // get the rotation information + UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis ); + + // get the animation state (after rotation, to allow feet shuffle) + UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + + renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; + + // + // add the legs + // + legs.hModel = pi->legsModel; + legs.customSkin = pi->legsSkin; + + VectorCopy( origin, legs.origin ); + + VectorCopy( origin, legs.lightingOrigin ); + legs.renderfx = renderfx; + VectorCopy (legs.origin, legs.oldorigin); + + trap_R_AddRefEntityToScene( &legs ); + + if (!legs.hModel) { + return; + } + + // + // add the torso + // + torso.hModel = pi->torsoModel; + if (!torso.hModel) { + return; + } + + torso.customSkin = pi->torsoSkin; + + VectorCopy( origin, torso.lightingOrigin ); + + UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, "tag_torso"); + + torso.renderfx = renderfx; + + trap_R_AddRefEntityToScene( &torso ); + + // + // add the head + // + head.hModel = pi->headModel; + if (!head.hModel) { + return; + } + head.customSkin = pi->headSkin; + + VectorCopy( origin, head.lightingOrigin ); + + UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head"); + + head.renderfx = renderfx; + + trap_R_AddRefEntityToScene( &head ); + + // + // add the gun + // + if ( pi->currentWeapon != WP_NONE ) { + memset( &gun, 0, sizeof(gun) ); + gun.hModel = pi->weaponModel; + VectorCopy( origin, gun.lightingOrigin ); + UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon"); + gun.renderfx = renderfx; + trap_R_AddRefEntityToScene( &gun ); + } + + // + // add the spinning barrel + // +/* + if ( pi->realWeapon == WP_MACHINEGUN || pi->realWeapon == WP_BFG ) { + vec3_t angles; + + memset( &barrel, 0, sizeof(barrel) ); + VectorCopy( origin, barrel.lightingOrigin ); + barrel.renderfx = renderfx; + + barrel.hModel = pi->barrelModel; + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = UI_MachinegunSpinAngle( pi ); + if(pi->realWeapon == WP_BFG ) { + angles[PITCH] = angles[ROLL]; + angles[ROLL] = 0; + } + AnglesToAxis( angles, barrel.axis ); + + UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel"); + + trap_R_AddRefEntityToScene( &barrel ); + } +*/ + // + // add muzzle flash + // + if ( dp_realtime <= pi->muzzleFlashTime ) { + if ( pi->flashModel ) { + memset( &flash, 0, sizeof(flash) ); + flash.hModel = pi->flashModel; + VectorCopy( origin, flash.lightingOrigin ); + UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash"); + flash.renderfx = renderfx; + trap_R_AddRefEntityToScene( &flash ); + } + + // make a dlight for the flash + if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) { + trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0], + pi->flashDlightColor[1], pi->flashDlightColor[2] ); + } + } + + // + // add the chat icon + // + if ( pi->chat ) { + UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) ); + } + + // + // add an accent light + // + origin[0] -= 100; // + = behind, - = in front + origin[1] += 100; // + = left, - = right + origin[2] += 100; // + = above, - = below + trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 ); + + origin[0] -= 100; + origin[1] -= 100; + origin[2] -= 100; + trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 ); + + trap_R_RenderScene( &refdef ); +} + +/* +========================== +UI_FileExists +========================== +*/ +static qboolean UI_FileExists(const char *filename) { + int len; + fileHandle_t f; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if (len>0) { + trap_FS_FCloseFile(f); + return qtrue; + } + return qfalse; +} + +/* +========================== +UI_FindClientHeadFile +========================== +*/ +static qboolean UI_FindClientHeadFile( char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { + char *team, *headsFolder; + int i; + + team = "default"; + + if ( headModelName[0] == '*' ) { + headsFolder = "heads/"; + headModelName++; + } + else { + headsFolder = ""; + } + while(1) { + for ( i = 0; i < 2; i++ ) { + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); + } + if ( UI_FileExists( filename ) ) { + return qtrue; + } + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); + } + if ( UI_FileExists( filename ) ) { + return qtrue; + } + if ( !teamName || !*teamName ) { + break; + } + } + // if tried the heads folder first + if ( headsFolder[0] ) { + break; + } + headsFolder = "heads/"; + } + + return qfalse; +} + +/* +========================== +UI_RegisterClientSkin +========================== +*/ +static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName , const char *teamName) { + char filename[MAX_QPATH*2]; + + if (teamName && *teamName) { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName ); + } else { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName ); + } + pi->legsSkin = trap_R_RegisterSkin( filename ); + if (!pi->legsSkin) { + if (teamName && *teamName) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/lower_%s.skin", modelName, teamName, skinName ); + } else { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower_%s.skin", modelName, skinName ); + } + pi->legsSkin = trap_R_RegisterSkin( filename ); + } + + if (teamName && *teamName) { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName ); + } else { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName ); + } + pi->torsoSkin = trap_R_RegisterSkin( filename ); + if (!pi->torsoSkin) { + if (teamName && *teamName) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/upper_%s.skin", modelName, teamName, skinName ); + } else { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper_%s.skin", modelName, skinName ); + } + pi->torsoSkin = trap_R_RegisterSkin( filename ); + } + + if ( UI_FindClientHeadFile( filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin" ) ) { + pi->headSkin = trap_R_RegisterSkin( filename ); + } + + if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) { + return qfalse; + } + + return qtrue; +} + + +/* +====================== +UI_ParseAnimationFile +====================== +*/ +static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) { + char *text_p, *prev; + int len; + int i; + char *token; + float fps; + int skip; + char text[20000]; + fileHandle_t f; + + memset( animations, 0, sizeof( animation_t ) * MAX_ANIMATIONS ); + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + if ( len >= ( sizeof( text ) - 1 ) ) { + Com_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress(text); + + // parse the text + text_p = text; + skip = 0; // quite the compiler warning + + // read optional parameters + while ( 1 ) { + prev = text_p; // so we can unget + token = COM_Parse( (const char **)&text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( (const char **)&text_p ); + if ( !token ) { + break; + } + continue; + } else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( (const char **)&text_p ); + if ( !token ) { + break; + } + } + continue; + } else if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( (const char **)&text_p ); + if ( !token ) { + break; + } + continue; + } + + // if it is a number, start parsing animations + if ( token[0] >= '0' && token[0] <= '9' ) { + text_p = prev; // unget the token + break; + } + + Com_Printf( "unknown token '%s' is %s\n", token, filename ); + } + + // read information for each frame + for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { + + token = COM_Parse( (const char **)&text_p ); + if ( !token ) { + break; + } + animations[i].firstFrame = atoi( token ); + // leg only frames are adjusted to not count the upper body only frames + if ( i == BOTH_CROUCH1WALK ) { + skip = animations[BOTH_CROUCH1WALK].firstFrame - animations[BOTH_GESTURE1].firstFrame; + } + if ( i >= BOTH_CROUCH1WALK ) { + animations[i].firstFrame -= skip; + } + + token = COM_Parse( (const char **)&text_p ); + if ( !token ) { + break; + } + animations[i].numFrames = atoi( token ); + + token = COM_Parse( (const char **)&text_p ); + if ( !token ) { + break; + } + animations[i].loopFrames = atoi( token ); + + token = COM_Parse( (const char **)&text_p ); + if ( !token ) { + break; + } + fps = atof( token ); + if ( fps == 0 ) { + fps = 1; + } + animations[i].frameLerp = 1000 / fps; + animations[i].initialLerp = 1000 / fps; + } + + if ( i != MAX_ANIMATIONS ) { + Com_Printf( "Error parsing animation file: %s", filename ); + return qfalse; + } + + return qtrue; +} + +/* +========================== +UI_RegisterClientModelname +========================== +*/ +qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName ) { + char modelName[MAX_QPATH]; + char skinName[MAX_QPATH]; + char headModelName[MAX_QPATH]; + char headSkinName[MAX_QPATH]; + char filename[MAX_QPATH]; + char *slash; + + pi->torsoModel = 0; + pi->headModel = 0; + + if ( !modelSkinName[0] ) { + return qfalse; + } + + Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) ); + + slash = strchr( modelName, '/' ); + if ( !slash ) { + // modelName did not include a skin name + Q_strncpyz( skinName, "default", sizeof( skinName ) ); + } else { + Q_strncpyz( skinName, slash + 1, sizeof( skinName ) ); + *slash = '\0'; + } + + Q_strncpyz( headModelName, headModelSkinName, sizeof( headModelName ) ); + slash = strchr( headModelName, '/' ); + if ( !slash ) { + // modelName did not include a skin name + Q_strncpyz( headSkinName, "default", sizeof( skinName ) ); + } else { + Q_strncpyz( headSkinName, slash + 1, sizeof( skinName ) ); + *slash = '\0'; + } + + // load cmodels before models so filecache works + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); + pi->legsModel = trap_R_RegisterModel( filename ); + if ( !pi->legsModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); + pi->legsModel = trap_R_RegisterModel( filename ); + if ( !pi->legsModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); + pi->torsoModel = trap_R_RegisterModel( filename ); + if ( !pi->torsoModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); + pi->torsoModel = trap_R_RegisterModel( filename ); + if ( !pi->torsoModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + if (headModelName && headModelName[0] == '*' ) { + Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); + } + else { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headModelName ); + } + pi->headModel = trap_R_RegisterModel( filename ); + if ( !pi->headModel && headModelName[0] != '*') { + Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); + pi->headModel = trap_R_RegisterModel( filename ); + } + + if (!pi->headModel) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + // if any skins failed to load, fall back to default + if ( !UI_RegisterClientSkin( pi, modelName, skinName, headModelName, headSkinName, teamName) ) { + if ( !UI_RegisterClientSkin( pi, modelName, "default", headModelName, "default", teamName ) ) { + Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName ); + return qfalse; + } + } + + // load the animations + Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); + if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); + if ( !UI_ParseAnimationFile( filename, pi->animations ) ) { + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; + } + } + + return qtrue; +} + + +/* +=============== +UI_PlayerInfo_SetModel +=============== +*/ +void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ) { + memset( pi, 0, sizeof(*pi) ); + UI_RegisterClientModelname( pi, model, headmodel, teamName ); + pi->weapon = WP_BRYAR_PISTOL; + pi->currentWeapon = pi->weapon; + pi->lastWeapon = pi->weapon; + pi->pendingWeapon = -1; + pi->weaponTimer = 0; + pi->chat = qfalse; + pi->newModel = qtrue; + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); +} + + +/* +=============== +UI_PlayerInfo_SetInfo +=============== +*/ +void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) { + int currentAnim; + weapon_t weaponNum; + + pi->chat = chat; + + // view angles + VectorCopy( viewAngles, pi->viewAngles ); + + // move angles + VectorCopy( moveAngles, pi->moveAngles ); + + if ( pi->newModel ) { + pi->newModel = qfalse; + + jumpHeight = 0; + pi->pendingLegsAnim = 0; + UI_ForceLegsAnim( pi, legsAnim ); + pi->legs.yawAngle = viewAngles[YAW]; + pi->legs.yawing = qfalse; + + pi->pendingTorsoAnim = 0; + UI_ForceTorsoAnim( pi, torsoAnim ); + pi->torso.yawAngle = viewAngles[YAW]; + pi->torso.yawing = qfalse; + + if ( weaponNumber != -1 ) { + pi->weapon = weaponNumber; + pi->currentWeapon = weaponNumber; + pi->lastWeapon = weaponNumber; + pi->pendingWeapon = -1; + pi->weaponTimer = 0; + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); + } + + return; + } + + // weapon + if ( weaponNumber == -1 ) { + pi->pendingWeapon = -1; + pi->weaponTimer = 0; + } + else if ( weaponNumber != WP_NONE ) { + pi->pendingWeapon = weaponNumber; + pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY; + } + weaponNum = pi->lastWeapon; + pi->weapon = weaponNum; + + if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) { + torsoAnim = legsAnim = BOTH_DEATH1; + pi->weapon = pi->currentWeapon = WP_NONE; + UI_PlayerInfo_SetWeapon( pi, pi->weapon ); + + jumpHeight = 0; + pi->pendingLegsAnim = 0; + UI_ForceLegsAnim( pi, legsAnim ); + + pi->pendingTorsoAnim = 0; + UI_ForceTorsoAnim( pi, torsoAnim ); + + return; + } + + // leg animation + currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; + if ( legsAnim != BOTH_JUMP1 && ( currentAnim == BOTH_JUMP1 || currentAnim == BOTH_LAND1 ) ) { + pi->pendingLegsAnim = legsAnim; + } + else if ( legsAnim != currentAnim ) { + jumpHeight = 0; + pi->pendingLegsAnim = 0; + UI_ForceLegsAnim( pi, legsAnim ); + } + + // torso animation + if ( torsoAnim == TORSO_WEAPONREADY3 || torsoAnim == BOTH_STAND2 ) { + if ( weaponNum == WP_NONE || weaponNum == WP_SABER ) { + torsoAnim = BOTH_STAND2; + } + else { + torsoAnim = TORSO_WEAPONREADY3; + } + } + + if ( torsoAnim == BOTH_ATTACK3 || torsoAnim == BOTH_A1_T__B_ ) { + if ( weaponNum == WP_NONE || weaponNum == WP_SABER ) { + torsoAnim = BOTH_A1_T__B_; + } + else { + torsoAnim = BOTH_ATTACK3; + } + pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH; + //FIXME play firing sound here + } + + currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; + + if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISEWEAP1|| currentAnim == TORSO_DROPWEAP1 ) { + pi->pendingTorsoAnim = torsoAnim; + } + else if ( ( currentAnim == BOTH_GESTURE1 || currentAnim == BOTH_ATTACK3 ) && ( torsoAnim != currentAnim ) ) { + pi->pendingTorsoAnim = torsoAnim; + } + else if ( torsoAnim != currentAnim ) { + pi->pendingTorsoAnim = 0; + UI_ForceTorsoAnim( pi, torsoAnim ); + } +} diff --git a/codemp/ui/ui_public.h b/codemp/ui/ui_public.h new file mode 100644 index 0000000..5c9ae58 --- /dev/null +++ b/codemp/ui/ui_public.h @@ -0,0 +1,252 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#ifndef __UI_PUBLIC_H__ +#define __UI_PUBLIC_H__ + +#define UI_API_VERSION 7 + +typedef struct { + connstate_t connState; + int connectPacketCount; + int clientNum; + char servername[MAX_STRING_CHARS]; + char updateInfoString[MAX_STRING_CHARS]; + char messageString[MAX_STRING_CHARS]; +} uiClientState_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, + 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, + UI_R_SHADERNAMEFROMINDEX, + 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, + 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, + 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, + UI_CVAR_UPDATE, + UI_MEMORY_REMAINING, + UI_GET_CDKEY, + UI_SET_CDKEY, + UI_VERIFY_CDKEY, + UI_R_REGISTERFONT, + UI_R_FONT_STRLENPIXELS, + UI_R_FONT_STRLENCHARS, + UI_R_FONT_STRHEIGHTPIXELS, + UI_R_FONT_DRAWSTRING, + UI_LANGUAGE_ISASIAN, + UI_LANGUAGE_USESSPACES, + UI_ANYLANGUAGE_READCHARFROMSTRING, + UI_R_MODELBOUNDS, + UI_PC_ADD_GLOBAL_DEFINE, + UI_PC_LOAD_SOURCE, + UI_PC_FREE_SOURCE, + UI_PC_READ_TOKEN, + UI_PC_SOURCE_FILE_AND_LINE, + UI_PC_LOAD_GLOBAL_DEFINES, + UI_PC_REMOVE_ALL_GLOBAL_DEFINES, + + 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, + 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, + 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_MATRIXMULTIPLY, + UI_ANGLEVECTORS, + UI_PERPENDICULARVECTOR, + UI_FLOOR, + UI_CEIL, + + UI_TESTPRINTINT, + UI_TESTPRINTFLOAT, + + UI_ACOS, + UI_ASIN, + + UI_SP_GETNUMLANGUAGES, + UI_SP_GETLANGUAGENAME, + UI_SP_GETSTRINGTEXTSTRING = 200, + +/* +Ghoul2 Insert Start +*/ + UI_G2_LISTSURFACES, + UI_G2_LISTBONES, + UI_G2_SETMODELS, + UI_G2_HAVEWEGHOULMODELS, + UI_G2_GETBOLT, + UI_G2_GETBOLT_NOREC, + UI_G2_GETBOLT_NOREC_NOROT, + UI_G2_INITGHOUL2MODEL, + UI_G2_COLLISIONDETECT, + UI_G2_CLEANMODELS, + UI_G2_ANGLEOVERRIDE, + UI_G2_PLAYANIM, + UI_G2_GETBONEANIM, + UI_G2_GETBONEFRAME, //trimmed down version of GBA, so I don't have to pass all those unused args across the VM-exe border + UI_G2_GETGLANAME, + UI_G2_COPYGHOUL2INSTANCE, + UI_G2_COPYSPECIFICGHOUL2MODEL, + UI_G2_DUPLICATEGHOUL2INSTANCE, + UI_G2_HASGHOUL2MODELONINDEX, + UI_G2_REMOVEGHOUL2MODEL, + UI_G2_ADDBOLT, + UI_G2_SETBOLTON, + UI_G2_SETROOTSURFACE, + UI_G2_SETSURFACEONOFF, + UI_G2_SETNEWORIGIN, + + UI_G2_GETTIME, + UI_G2_SETTIME, + +/* + //rww - RAGDOLL_BEGIN +*/ + UI_G2_SETRAGDOLL, + UI_G2_ANIMATEG2MODELS, +/* + //rww - RAGDOLL_END +*/ + + //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. + UI_G2_SETBONEIKSTATE, + UI_G2_IKMOVE, + + UI_G2_GETSURFACENAME, + UI_G2_SETSKIN, + UI_G2_ATTACHG2MODEL, +/* +Ghoul2 Insert End +*/ +} uiImport_t; + +typedef enum { + UIMENU_NONE, + UIMENU_MAIN, + UIMENU_INGAME, + UIMENU_PLAYERCONFIG, + UIMENU_TEAM, + UIMENU_POSTGAME, + UIMENU_PLAYERFORCE, + UIMENU_SIEGEMESSAGE, + UIMENU_SIEGEOBJECTIVES, + UIMENU_VOICECHAT, + UIMENU_CLOSEALL, + UIMENU_CLASSSEL +}; +typedef int uiMenuCommand_t; + +#define SORT_HOST 0 +#define SORT_MAP 1 +#define SORT_CLIENTS 2 +#define SORT_GAME 3 +#define SORT_PING 4 + +typedef enum { + UI_GETAPIVERSION = 0, // system reserved + + UI_INIT, +// void UI_Init( void ); + + UI_SHUTDOWN, +// void UI_Shutdown( void ); + + UI_KEY_EVENT, +// void UI_KeyEvent( int key ); + + UI_MOUSE_EVENT, +// void UI_MouseEvent( int dx, int dy ); + + UI_REFRESH, +// void UI_Refresh( int time ); + + UI_IS_FULLSCREEN, +// qboolean UI_IsFullscreen( void ); + + UI_SET_ACTIVE_MENU, +// void UI_SetActiveMenu( uiMenuCommand_t menu ); + + UI_CONSOLE_COMMAND, +// qboolean UI_ConsoleCommand( int realTime ); + + UI_DRAW_CONNECT_SCREEN, +// void UI_DrawConnectScreen( qboolean overlay ); + UI_HASUNIQUECDKEY, +// if !overlay, the background will be drawn, otherwise it will be +// overlayed over whatever the cgame has drawn. +// a GetClientState syscall will be made to get the current strings + + UI_MENU_RESET +} uiExport_t; + +#endif diff --git a/codemp/ui/ui_saber.c b/codemp/ui/ui_saber.c new file mode 100644 index 0000000..ce230b8 --- /dev/null +++ b/codemp/ui/ui_saber.c @@ -0,0 +1,1107 @@ +// +/* +======================================================================= + +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" + +//#define MAX_SABER_DATA_SIZE 0x8000 +#define MAX_SABER_DATA_SIZE 0x9000 + +// On Xbox, static linking lets us steal the buffer from wp_saberLoad +// Just make sure that the saber data size is the same +// Damn. OK. Gotta fix this again. Later. +static char SaberParms[MAX_SABER_DATA_SIZE]; +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 = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/red_glow" ); + redSaberCoreShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/red_line" ); + orangeSaberGlowShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/orange_glow" ); + orangeSaberCoreShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/orange_line" ); + yellowSaberGlowShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/yellow_glow" ); + yellowSaberCoreShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/yellow_line" ); + greenSaberGlowShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/green_glow" ); + greenSaberCoreShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/green_line" ); + blueSaberGlowShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/blue_glow" ); + blueSaberCoreShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/blue_line" ); + purpleSaberGlowShader = trap_R_RegisterShaderNoMip( "gfx/effects/sabers/purple_glow" ); + purpleSaberCoreShader = trap_R_RegisterShaderNoMip( "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 ) + { + Com_Printf( "unexpected EOF\n" ); + return qtrue; + } + + if ( Q_stricmp( token, string ) ) + { + Com_Printf( "required string '%s' missing\n", string ); + return qtrue; + } + + return qfalse; +} + +qboolean UI_ParseLiteralSilent( const char **data, const char *string ) +{ + const char *token; + + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) + { + return qtrue; + } + + if ( Q_stricmp( token, 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; + // A bogus name is passed in + 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 ( UI_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 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 ) +{ + int numBlades; + char numBladesString[8]={0}; + UI_SaberParseParm( saberName, "numBlades", numBladesString ); + numBlades = atoi( numBladesString ); + if ( numBlades < 1 ) + { + numBlades = 1; + } + else if ( numBlades > 8 ) + { + numBlades = 8; + } + return numBlades; +} + + +qboolean UI_IsSaberTwoHanded( const char *saberName ) +{ + int twoHanded; + char twoHandedString[8]={0}; + UI_SaberParseParm( saberName, "twoHanded", twoHandedString ); + if ( !twoHandedString[0] ) + {//not defined defaults to "no" + return qfalse; + } + twoHanded = atoi( twoHandedString ); + return ((qboolean)(twoHanded!=0)); +} + +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; +} + +qboolean UI_SaberProperNameForSaber( const char *saberName, char *saberProperName ) +{ + char stringedSaberName[1024]; + qboolean ret = UI_SaberParseParm( saberName, "name", stringedSaberName ); + // if it's a stringed reference translate it + if( ret && stringedSaberName && stringedSaberName[0] == '@') + { + trap_SP_GetStringTextString(&stringedSaberName[1], saberProperName, 1024); + } + else + { + // no stringed so just use it as it + strcpy( saberProperName, stringedSaberName ); + } + + return ret; + +} + +qboolean UI_SaberValidForPlayerInMP( const char *saberName ) +{ + char allowed [8]={0}; + if ( !UI_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 UI_SaberLoadParms( void ) +{ + int len, totallen, saberExtFNLen, fileCnt, i; + char *holdChar, *marker; + char saberExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + char buffer[MAX_MENUFILE]; + + //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 extra .npc 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 (!f) + { + continue; + } + + if ( len == -1 ) + { + Com_Printf( "UI_SaberLoadParms: error reading %s\n", holdChar ); + } + else + { + if (len > sizeof(buffer) ) + { + Com_Error( ERR_FATAL, "UI_SaberLoadParms: file %s too large to read (max=%d)", holdChar, sizeof(buffer) ); + } + trap_FS_Read( buffer, len, f ); + trap_FS_FCloseFile( f ); + buffer[len] = 0; + + 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 .sab files smaller)", holdChar ); + } + strcat( marker, 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; + 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 = 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; + } + + 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; + + trap_R_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; + + trap_R_AddRefEntityToScene( &saber ); +} + +char * SaberColorToString(saber_colors_t color) +{ + if ( color == SABER_RED) + return "red"; + + if ( color == SABER_ORANGE) + return "orange"; + + if ( color == SABER_YELLOW) + return "yellow"; + + if ( color == SABER_GREEN) + return "green"; + + if (color == SABER_BLUE) + return "blue"; + + if ( color == SABER_PURPLE) + return "purple"; + return NULL; +} +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, vec3_t angles, int bladeNum ) +{ + + char bladeColorString[MAX_QPATH]; + saber_colors_t bladeColor; + float bladeLength,bladeRadius; + vec3_t bladeOrigin={0}; + vec3_t axis[3]={0}; +// vec3_t angles={0}; + mdxaBone_t boltMatrix; + qboolean tagHack = qfalse; + char *tagName; + int bolt; + float scale; + + if ( (item->flags&ITF_ISSABER) && saberModel < 2 ) + { + trap_Cvar_VariableStringBuffer("ui_saber_color", bladeColorString, sizeof(bladeColorString) ); + } + else//if ( item->flags&ITF_ISSABER2 ) - presumed + { + trap_Cvar_VariableStringBuffer("ui_saber2_color", bladeColorString, sizeof(bladeColorString) ); + } + + if ( !trap_G2API_HasGhoul2ModelOnIndex(&(item->ghoul2),saberModel) ) + {//invalid index! + return; + } + + bladeColor = TranslateSaberColor( bladeColorString ); + + bladeLength = UI_SaberBladeLengthForSaber( saberName, bladeNum ); + bladeRadius = UI_SaberBladeRadiusForSaber( saberName, bladeNum ); + + tagName = va( "*blade%d", bladeNum+1 ); + bolt = trap_G2API_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 = trap_G2API_AddBolt( item->ghoul2,saberModel, "*flash" ); + if ( bolt == -1 ) + {//no tag_flash either?!! + bolt = 0; + } + } + +// angles[PITCH] = curYaw; +// angles[ROLL] = 0; + + trap_G2API_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. + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, bladeOrigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, axis[0]);//front (was NEGATIVE_Y, but the md3->glm exporter screws up this tag somethin' awful) + // ...changed this back to NEGATIVE_Y + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, axis[1]);//right ... and changed this to NEGATIVE_X + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, axis[2]);//up + + // Where do I get scale from? +// scale = DC->xscale; + scale = 1.0f; + + if ( tagHack ) + { + switch ( saberType ) + { + case SABER_SINGLE: + VectorMA( bladeOrigin, scale, axis[0], bladeOrigin ); + break; + case SABER_DAGGER: + case SABER_LANCE: + break; + case SABER_STAFF: + if ( bladeNum == 0 ) + { + VectorMA( bladeOrigin, 12*scale, axis[0], bladeOrigin ); + } + if ( bladeNum == 1 ) + { + VectorScale( axis[0], -1, axis[0] ); + VectorMA( bladeOrigin, 12*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 ); + +} + +/* +void UI_SaberDrawBlades( itemDef_t *item, vec3_t origin, vec3_t angles ) +{ + //NOTE: only allows one saber type in view at a time + char saber[MAX_QPATH]; + if ( item->flags&ITF_ISSABER ) + { + trap_Cvar_VariableStringBuffer("ui_saber", saber, sizeof(saber) ); + if ( !UI_SaberValidForPlayerInMP( saber ) ) + { + trap_Cvar_Set( "ui_saber", "kyle" ); + trap_Cvar_VariableStringBuffer("ui_saber", saber, sizeof(saber) ); + } + } + else if ( item->flags&ITF_ISSABER2 ) + { + trap_Cvar_VariableStringBuffer("ui_saber2", saber, sizeof(saber) ); + if ( !UI_SaberValidForPlayerInMP( saber ) ) + { + trap_Cvar_Set( "ui_saber2", "kyle" ); + trap_Cvar_VariableStringBuffer("ui_saber2", saber, sizeof(saber) ); + } + } + else + { + return; + } + if ( saber[0] ) + { + saberType_t saberType; + int curBlade; + 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 = TranslateSaberType( saberTypeString ); + for ( curBlade = 0; curBlade < numBlades; curBlade++ ) + { + UI_SaberDrawBlade( item, saber, saberType, origin, angles, curBlade ); + } + } + } +} +*/ + +void UI_GetSaberForMenu( char *saber, int saberNum ) +{ + char saberTypeString[MAX_QPATH]={0}; + saberType_t saberType = SABER_NONE; + + if ( saberNum == 0 ) + { + trap_Cvar_VariableStringBuffer("ui_saber", saber, MAX_QPATH ); + if ( !UI_SaberValidForPlayerInMP( saber ) ) + { + trap_Cvar_Set( "ui_saber", "kyle" ); + trap_Cvar_VariableStringBuffer("ui_saber", saber, MAX_QPATH ); + } + } + else + { + trap_Cvar_VariableStringBuffer("ui_saber2", saber, MAX_QPATH ); + if ( !UI_SaberValidForPlayerInMP( saber ) ) + { + trap_Cvar_Set( "ui_saber2", "kyle" ); + trap_Cvar_VariableStringBuffer("ui_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); + } + break; + case 4://MD_DUAL_SABERS: + if ( saberType != SABER_SINGLE ) + { + Q_strncpyz(saber,"single_1",MAX_QPATH); + } + break; + case 5://MD_SABER_STAFF: + if ( saberType == SABER_SINGLE || saberType == SABER_NONE ) + { + Q_strncpyz(saber,"dual_1",MAX_QPATH); + } + break; + } + +} + +void UI_SaberDrawBlades( itemDef_t *item, vec3_t origin, vec3_t angles ) +{ + //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) ) + { + trap_Cvar_VariableStringBuffer("ui_saber", saber, sizeof(saber) ); + if ( !UI_SaberValidForPlayerInMP( saber ) ) + { + trap_Cvar_Set( "ui_saber", "kyle" ); + trap_Cvar_VariableStringBuffer("ui_saber", saber, sizeof(saber) ); + } + saberModel = 0; + } + else if ( (item->flags&ITF_ISSABER2) ) + { + trap_Cvar_VariableStringBuffer("ui_saber2", saber, sizeof(saber) ); + if ( !UI_SaberValidForPlayerInMP( saber ) ) + { + trap_Cvar_Set( "ui_saber2", "kyle" ); + trap_Cvar_VariableStringBuffer("ui_saber2", saber, sizeof(saber) ); + } + saberModel = 0; + } + else + { + return; + } + if ( saber[0] ) + { + saberType_t saberType; + int curBlade = 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 = TranslateSaberType( saberTypeString ); + for ( curBlade = 0; curBlade < numBlades; curBlade++ ) + { + UI_SaberDrawBlade( item, saber, saberModel, saberType, origin, angles, curBlade ); + } + } + } + } +} + +void UI_SaberAttachToChar( itemDef_t *item ) +{ + int numSabers = 1; + int saberNum = 0; + + if ( trap_G2API_HasGhoul2ModelOnIndex(&(item->ghoul2),2) ) + {//remove any extra models + trap_G2API_RemoveGhoul2Model(&(item->ghoul2), 2); + } + if ( trap_G2API_HasGhoul2ModelOnIndex(&(item->ghoul2),1) ) + {//remove any extra models + trap_G2API_RemoveGhoul2Model(&(item->ghoul2), 1); + } + + if ( uiInfo.movesTitleIndex == 4 /*MD_DUAL_SABERS*/ ) + { + 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 = trap_G2API_InitGhoul2Model( &(item->ghoul2), modelPath, 0, 0, 0, 0, 0 ); //add the model + if ( g2Saber ) + { + int boltNum; + //get the customSkin, if any + if ( UI_SaberSkinForSaber( saber, skinPath ) ) + { + int g2skin = trap_R_RegisterSkin(skinPath); + trap_G2API_SetSkin( item->ghoul2, g2Saber, 0, g2skin );//this is going to set the surfs on/off matching the skin file + } + else + { + trap_G2API_SetSkin( item->ghoul2, g2Saber, 0, 0 );//turn off custom skin + } + if ( saberNum == 0 ) + { + boltNum = trap_G2API_AddBolt( item->ghoul2, 0, "*r_hand"); + } + else + { + boltNum = trap_G2API_AddBolt( item->ghoul2, 0, "*l_hand"); + } + trap_G2API_AttachG2Model( item->ghoul2, g2Saber, item->ghoul2, boltNum, 0); + } + } + } +} + +#define MAX_SABER_HILTS 64 + +// Fill in with saber hilts +void UI_SaberGetHiltInfo( const char *singleHilts[MAX_SABER_HILTS], const char *staffHilts[MAX_SABER_HILTS] ) +{ + int numSingleHilts = 0, numStaffHilts = 0; + const char *saberName; + const char *token; + const char *p; + + //go through all the loaded sabers and put the valid ones in the proper list + p = SaberParms; + COM_BeginParseSession("saberlist"); + + // look for a saber + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + {//invalid name + continue; + } + saberName = String_Alloc( token ); + //see if there's a "{" on the next line + SkipRestOfLine( &p ); + + if ( UI_ParseLiteralSilent( &p, "{" ) ) + {//nope, not a name, keep looking + continue; + } + + //this is a saber name + if ( !UI_SaberValidForPlayerInMP( saberName ) ) + { + SkipBracedSection( &p ); + continue; + } + + if ( UI_IsSaberTwoHanded( saberName ) ) + { + if ( numStaffHilts < MAX_SABER_HILTS-1 )//-1 because we have to NULL terminate the list + { + staffHilts[numStaffHilts++] = saberName; + } + else + { + Com_Printf( "WARNING: too many two-handed sabers, ignoring saber '%s'\n", saberName ); + } + } + else + { + if ( numSingleHilts < MAX_SABER_HILTS-1 )//-1 because we have to NULL terminate the list + { + singleHilts[numSingleHilts++] = saberName; + } + else + { + Com_Printf( "WARNING: too many one-handed sabers, ignoring saber '%s'\n", saberName ); + } + } + //skip the whole braced section and move on to the next entry + SkipBracedSection( &p ); + } + //null terminate the list so the UI code knows where to stop listing them + singleHilts[numSingleHilts] = NULL; + staffHilts[numStaffHilts] = NULL; +} \ No newline at end of file diff --git a/codemp/ui/ui_shared.c b/codemp/ui/ui_shared.c new file mode 100644 index 0000000..55fe8de --- /dev/null +++ b/codemp/ui/ui_shared.c @@ -0,0 +1,10157 @@ +// +// string allocation/managment + +#ifndef CGAME + #include "ui_local.h" +#endif +#ifdef _XBOX +#include "../client/client.h" +#endif + +#include "ui_shared.h" +#include "../game/bg_public.h" +#include "../game/anims.h" +#include "../ghoul2/G2.h" +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; +extern void UI_UpdateCharacterSkin( void ); + + +#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; + +#ifdef _XBOX +//extern void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign); +//extern void Z_TagFree(memtag_t eTag); +#endif + + +#ifndef CGAME // Defined in ui_main.c, not in the namespace +extern vmCvar_t ui_char_color_red; +extern vmCvar_t ui_char_color_green; +extern vmCvar_t ui_char_color_blue; +extern vmCvar_t se_language; + +// Some extern functions hoisted from the middle of this file to get all the non-cgame, +// non-namespace stuff together +extern void UI_SaberDrawBlades( itemDef_t *item, vec3_t origin, vec3_t angles ); + +extern void UI_SaberLoadParms( void ); +extern qboolean ui_saber_parms_parsed; +extern void UI_CacheSaberGlowGraphics( void ); + +#endif // + +#include "../namespace_begin.h" + +#ifdef CGAME + +extern int trap_Key_GetCatcher( void ) ; +extern void trap_Key_SetCatcher( int catcher ); +extern void trap_Cvar_Set( const char *var_name, const char *value ); + +#endif + +//JLF DEMOCODE +#ifdef _XBOX + +//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_DemoFrame(); +void G_DemoKeypress(); + +void PlayDemo(); +//void UpdateDemoTimer(); +bool TestDemoTimer(); +//END DEMOCODE + +//JLF used by sliders +#define TICK_COUNT 20 + +//JLF MORE PROTOTYPES +qboolean Item_HandleSelectionNext(itemDef_t * item); +qboolean Item_HandleSelectionPrev(itemDef_t * item); + +#endif // _XBOX + +qboolean Item_SetFocus(itemDef_t *item, float x, float y); + +static scrollInfo_t scrollInfo; + +static void (*captureFunc) (void *p) = 0; +static void *captureData = NULL; +static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any ) + +displayContextDef_t *DC = NULL; + +static qboolean g_waitingForKey = qfalse; +static qboolean g_editingField = qfalse; + +static itemDef_t *g_bindItem = NULL; +static itemDef_t *g_editItem = NULL; + +menuDef_t Menus[MAX_MENUS]; // defined menus +int menuCount = 0; // how many + +menuDef_t *menuStack[MAX_OPEN_MENUS]; +int openMenuCount = 0; + +static qboolean debugMode = qfalse; + +#define DOUBLE_CLICK_DELAY 300 +static int lastListBoxClickTime = 0; + +void Item_RunScript(itemDef_t *item, const char *s); +void Item_SetupKeywordHash(void); +void Menu_SetupKeywordHash(void); +int BindingIDFromName(const char *name); +qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down); +itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu); +itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu); +static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y); +static void Item_TextScroll_BuildLines ( itemDef_t* item ); +void Menu_SetItemText(const menuDef_t *menu,const char *itemName, const char *text); +extern qboolean ItemParse_asset_model_go( itemDef_t *item, const char *name,int *runTimeLength ); +extern qboolean ItemParse_model_g2anim_go( itemDef_t *item, const char *animName ); + + +#ifdef CGAME +#define MEM_POOL_SIZE 128 * 1024 +#define UI_ALLOCATION_TAG TAG_CG_UI_ALLOC +#else +//#define MEM_POOL_SIZE 1024 * 1024 +#define MEM_POOL_SIZE 2048 * 1024 +#define UI_ALLOCATION_TAG TAG_UI_ALLOC +#endif + +#ifndef _XBOX +static char memoryPool[MEM_POOL_SIZE]; +#endif // _XBOX + +static int allocPoint, outOfMemory; + + +typedef struct itemFlagsDef_s { + char *string; + int value; +} itemFlagsDef_t; + +itemFlagsDef_t itemFlags [] = { +"WINDOW_INACTIVE", WINDOW_INACTIVE, +NULL, (int) NULL +}; + +char *styles [] = { +"WINDOW_STYLE_EMPTY", +"WINDOW_STYLE_FILLED", +"WINDOW_STYLE_GRADIENT", +"WINDOW_STYLE_SHADER", +"WINDOW_STYLE_TEAMCOLOR", +"WINDOW_STYLE_CINEMATIC", +NULL +}; + +char *alignment [] = { +"ITEM_ALIGN_LEFT", +"ITEM_ALIGN_CENTER", +"ITEM_ALIGN_RIGHT", +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 +}; + + +extern int MenuFontToHandle(int iMenuFont); + + + + +/* +=============== +UI_Alloc +=============== +*/ +void *UI_Alloc( int size ) { +#ifdef _XBOX + + allocPoint += size; + return Z_Malloc(size, UI_ALLOCATION_TAG, qfalse, 4); + +#else // _XBOX + + char *p; + + if ( allocPoint + size > MEM_POOL_SIZE ) { + outOfMemory = qtrue; + if (DC->Print) { + DC->Print("UI_Alloc: Failure. Out of memory!\n"); + } + //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI 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(UI_ALLOCATION_TAG); +#endif +} + +qboolean UI_OutOfMemory() { + return outOfMemory; +} + + + + + +#define HASH_TABLE_SIZE 2048 +/* +================ +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; +} + +typedef struct stringDef_s { + struct stringDef_s *next; + const char *str; +} stringDef_t; + +static int strPoolIndex = 0; +static char strPool[STRING_POOL_SIZE]; + +static int strHandleCount = 0; +static stringDef_t *strHandle[HASH_TABLE_SIZE]; + + +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_t *) UI_Alloc(sizeof(stringDef_t)); + str->next = NULL; + str->str = &strPool[ph]; + if (last) { + last->next = str; + } else { + strHandle[hash] = str; + } + return &strPool[ph]; + } + + //Increase STRING_POOL_SIZE. + assert(0); + return NULL; +} + +void String_Report() { + 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); + 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); +} + +/* +================= +String_Init +================= +*/ +void String_Init() { + int i; + for (i = 0; i < HASH_TABLE_SIZE; i++) { + strHandle[i] = 0; + } + strHandleCount = 0; + strPoolIndex = 0; + menuCount = 0; + openMenuCount = 0; + UI_InitMemory(); + Item_SetupKeywordHash(); + Menu_SetupKeywordHash(); + if (DC && DC->getBindingBuf) { + Controls_GetConfig(); + } +} + +/* +================= +PC_SourceWarning +================= +*/ +void PC_SourceWarning(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; + trap_PC_SourceFileAndLine(handle, filename, &line); + + Com_Printf(S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string); +} + +/* +================= +PC_SourceError +================= +*/ +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; + trap_PC_SourceFileAndLine(handle, filename, &line); + + Com_Printf(S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string); +} + +/* +================= +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; + } +} + +/* +================= +Float_Parse +================= +*/ +qboolean Float_Parse(char **p, float *f) { + char *token; + token = COM_ParseExt((const char **)p, qfalse); + if (token && token[0] != 0) { + *f = atof(token); + return qtrue; + } else { + return qfalse; + } +} + +/* +================= +PC_Float_Parse +================= +*/ +qboolean PC_Float_Parse(int handle, float *f) { + pc_token_t token; + int negative = qfalse; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (token.string[0] == '-') { + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + negative = qtrue; + } + if (token.type != TT_NUMBER) { + PC_SourceError(handle, "expected float but found %s\n", token.string); + return qfalse; + } + if (negative) + *f = -token.floatvalue; + else + *f = token.floatvalue; + return qtrue; +} + +/* +================= +Color_Parse +================= +*/ +qboolean Color_Parse(char **p, vec4_t *c) { + int i; + float f; + + for (i = 0; i < 4; i++) { + if (!Float_Parse(p, &f)) { + return qfalse; + } + (*c)[i] = f; + } + return qtrue; +} + +/* +================= +PC_Color_Parse +================= +*/ +qboolean PC_Color_Parse(int handle, vec4_t *c) { + int i; + float f; + + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &f)) { + return qfalse; + } + (*c)[i] = f; + } + return qtrue; +} + +/* +================= +Int_Parse +================= +*/ +qboolean Int_Parse(char **p, int *i) { + char *token; + token = COM_ParseExt((const char **)p, qfalse); + + if (token && token[0] != 0) { + *i = atoi(token); + return qtrue; + } else { + return qfalse; + } +} + +/* +================= +PC_Int_Parse +================= +*/ +qboolean PC_Int_Parse(int handle, int *i) { + pc_token_t token; + int negative = qfalse; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (token.string[0] == '-') { + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + negative = qtrue; + } + if (token.type != TT_NUMBER) { + PC_SourceError(handle, "expected integer but found %s\n", token.string); + return qfalse; + } + *i = token.intvalue; + if (negative) + *i = - *i; + return qtrue; +} + +/* +================= +Rect_Parse +================= +*/ +qboolean Rect_Parse(char **p, rectDef_t *r) { + if (Float_Parse(p, &r->x)) { + if (Float_Parse(p, &r->y)) { + if (Float_Parse(p, &r->w)) { + if (Float_Parse(p, &r->h)) { + return qtrue; + } + } + } + } + return qfalse; +} + +/* +================= +PC_Rect_Parse +================= +*/ +qboolean PC_Rect_Parse(int handle, rectDef_t *r) { + if (PC_Float_Parse(handle, &r->x)) { + if (PC_Float_Parse(handle, &r->y)) { + if (PC_Float_Parse(handle, &r->w)) { + if (PC_Float_Parse(handle, &r->h)) { + return qtrue; + } + } + } + } + return qfalse; +} + +/* +================= +String_Parse +================= +*/ +qboolean String_Parse(char **p, const char **out) { + char *token; + + token = COM_ParseExt((const char **)p, qfalse); + if (token && token[0] != 0) { + *(out) = String_Alloc(token); + return *(out)!=NULL; + } + return qfalse; +} + +/* +================= +PC_String_Parse +================= +*/ +qboolean PC_String_Parse(int handle, const char **out) +{ + static char* squiggy = "}"; + pc_token_t token; + + if (!trap_PC_ReadToken(handle, &token)) + { + return qfalse; + } + + // Save some memory by not return the end squiggy as an allocated string + if ( !Q_stricmp ( token.string, "}" ) ) + { + *(out) = squiggy; + } + else + { + *(out) = String_Alloc(token.string); + } + return qtrue; +} + +/* +================= +PC_Script_Parse +================= +*/ +qboolean PC_Script_Parse(int handle, const char **out) { + char script[2048]; + pc_token_t token; + + 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 + + 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) { + *out = String_Alloc(script); + return qtrue; + } + + if (token.string[1] != '\0') { + Q_strcat(script, 2048, va("\"%s\"", token.string)); + } else { + Q_strcat(script, 2048, token.string); + } + Q_strcat(script, 2048, " "); + } + return qfalse; // bk001105 - LCC missing return value +} + +// display, window, menu, item code +// + +/* +================== +Init_Display + +Initializes the display with a structure to all the drawing routines + ================== +*/ +void Init_Display(displayContextDef_t *dc) { + DC = dc; +} + + + +// type and style painting + +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_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; +} + +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; + } + } + } + } + } +} + + + +void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle) +{ + //float bordersize = 0; + vec4_t color; + rectDef_t fillRect = w->rect; + + + if (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) + { +#ifndef CGAME + 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; + DC->setColor(color); + } +#endif // + + if (w->flags & WINDOW_FORECOLORSET) + { + DC->setColor(w->foreColor); + } + DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background); + DC->setColor(NULL); + } + else if (w->style == WINDOW_STYLE_TEAMCOLOR) + { + if (DC->getTeamColor) + { + DC->getTeamColor(&color); + DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, color); + } + } + else if (w->style == WINDOW_STYLE_CINEMATIC) + { + if (w->cinematic == -1) + { + w->cinematic = DC->playCinematic(w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h); + if (w->cinematic == -1) + { + w->cinematic = -2; + } + } + if (w->cinematic >= 0) + { + DC->runCinematicFrame(w->cinematic); + DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h); + } + } + + 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 + { + DC->drawRect(w->rect.x, w->rect.y, w->rect.w, 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); + } +} + + +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; + } + } +} + +// 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); + +} + +// menus +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); + } +} + +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); +} + +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; +} + +qboolean IsVisible(int flags) { + return (flags & WINDOW_VISIBLE && !(flags & WINDOW_FADINGOUT)); +} + +qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y) { + if (rect) { + if (x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h) { + return qtrue; + } + } + return qfalse; +} + +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; +} + +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; +} + +qboolean Script_SetColor ( itemDef_t *item, 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)) + { + return qtrue; + } + (*out)[i] = f; + } + } + } + + return qtrue; +} + +qboolean Script_SetAsset(itemDef_t *item, 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; +} + +qboolean Script_SetBackground(itemDef_t *item, char **args) +{ + const char *name; + // expecting name to set asset to + if (String_Parse(args, &name)) + { + item->window.background = DC->registerShaderNoMip(name); + } + return qtrue; +} + +qboolean Script_SetItemRectCvar(itemDef_t *item, char **args) +{ + const char *itemName; + const char *cvarName; + char cvarBuf[1024]; + const char *holdVal; + char *holdBuf; + itemDef_t *item2=0; + menuDef_t *menu; + + // expecting item group and cvar to get value from + if (String_Parse(args, &itemName) && String_Parse(args, &cvarName)) + { + item2 = Menu_FindItemByName((menuDef_t *) item->parent, itemName); + + if (item2) + { + // get cvar data + DC->getCVarString(cvarName, cvarBuf, sizeof(cvarBuf)); + + holdBuf = cvarBuf; + if (String_Parse(&holdBuf,&holdVal)) + { + menu = (menuDef_t *) item->parent; + + item2->window.rectClient.x = atof(holdVal) + menu->window.rect.x; + if (String_Parse(&holdBuf,&holdVal)) + { + item2->window.rectClient.y = atof(holdVal) + menu->window.rect.y; + if (String_Parse(&holdBuf,&holdVal)) + { + item2->window.rectClient.w = atof(holdVal); + if (String_Parse(&holdBuf,&holdVal)) + { + item2->window.rectClient.h = atof(holdVal); + + item2->window.rect.x = item2->window.rectClient.x; + item2->window.rect.y = item2->window.rectClient.y; + item2->window.rect.w = item2->window.rectClient.w; + item2->window.rect.h = item2->window.rectClient.h; + + return qtrue; + } + } + } + } + } + } + + // Default values in case things screw up + if (item2) + { + item2->window.rectClient.x = 0; + item2->window.rectClient.y = 0; + item2->window.rectClient.w = 0; + item2->window.rectClient.h = 0; + } + +// Com_Printf(S_COLOR_YELLOW"WARNING: SetItemRectCvar: problems. Set cvar to 0's\n" ); + + return qtrue; +} + +qboolean Script_SetItemBackground(itemDef_t *item, 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; +} + +qboolean Script_SetItemText(itemDef_t *item, 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; +} + + +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; +} + +qboolean Script_SetTeamColor(itemDef_t *item, char **args) +{ + if (DC->getTeamColor) + { + int i; + vec4_t color; + DC->getTeamColor(&color); + for (i = 0; i < 4; i++) + { + item->window.backColor[i] = color[i]; + } + } + return qtrue; +} + +qboolean Script_SetItemColor(itemDef_t *item, char **args) +{ + const char *itemname; + const char *name; + vec4_t color; + int i; + vec4_t *out; + + // expecting type of color to set and 4 args for the color + if (String_Parse(args, &itemname) && String_Parse(args, &name)) + { + itemDef_t *item2; + int j,count; + char buff[1024]; + + // Is is specifying a cvar to get the item name from? + if (itemname[0] == '*') + { + itemname += 1; + DC->getCVarString(itemname, buff, sizeof(buff)); + itemname = buff; + } + + count = Menu_ItemsMatchingGroup((menuDef_t *) item->parent, itemname); + + if (!Color_Parse(args, &color)) + { + return qtrue; + } + + 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]; + } + } + } + } + } + + return qtrue; +} + +qboolean Script_SetItemColorCvar(itemDef_t *item, char **args) +{ + const char *itemname; + char *colorCvarName,*holdBuf,*holdVal; + char cvarBuf[1024]; + const char *name; + vec4_t color; + int i; + vec4_t *out; + + // expecting type of color to set and 4 args for the color + if (String_Parse(args, &itemname) && String_Parse(args, &name)) + { + itemDef_t *item2; + int j,count; + char buff[1024]; + + // Is is specifying a cvar to get the item name from? + if (itemname[0] == '*') + { + itemname += 1; + DC->getCVarString(itemname, buff, sizeof(buff)); + itemname = buff; + } + + count = Menu_ItemsMatchingGroup((menuDef_t *) item->parent, itemname); + + // Get the cvar with the color + if (!String_Parse(args,(const char **) &colorCvarName)) + { + return qtrue; + } + else + { + DC->getCVarString(colorCvarName, cvarBuf, sizeof(cvarBuf)); + + holdBuf = cvarBuf; + if (String_Parse(&holdBuf,(const char **) &holdVal)) + { + color[0] = atof(holdVal); + if (String_Parse(&holdBuf,(const char **) &holdVal)) + { + color[1] = atof(holdVal); + if (String_Parse(&holdBuf,(const char **) &holdVal)) + { + color[2] = atof(holdVal); + if (String_Parse(&holdBuf,(const char **) &holdVal)) + { + color[3] = atof(holdVal); + } + } + } + } + } + + 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]; + } + } + } + } + } + + return qtrue; +} + +qboolean Script_SetItemRect(itemDef_t *item, char **args) +{ + const char *itemname; + rectDef_t *out; + rectDef_t rect; + menuDef_t *menu; + + // 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; + } + + menu = (menuDef_t *) item->parent; + + for (j = 0; j < count; j++) + { + item2 = Menu_GetMatchingItemByNumber(menu, j, itemname); + if (item2 != NULL) + { + out = &item2->window.rect; + + if (out) + { + item2->window.rect.x = rect.x + menu->window.rect.x; + item2->window.rect.y = rect.y + menu->window.rect.y; + item2->window.rect.w = rect.w; + item2->window.rect.h = rect.h; + } + } + } + } + return qtrue; +} + +void Menu_ShowGroup (menuDef_t *menu, char *groupName, qboolean showFlag) +{ + itemDef_t *item; + int count,j; + + count = Menu_ItemsMatchingGroup( menu, groupName); + for (j = 0; j < count; j++) + { + item = Menu_GetMatchingItemByNumber( menu, j, groupName); + if (item != NULL) + { + if (showFlag) + { + item->window.flags |= WINDOW_VISIBLE; + } + else + { + item->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS); + } + } + } +} + +void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow) { + 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 (bShow) { + item->window.flags |= WINDOW_VISIBLE; + } else { + item->window.flags &= ~WINDOW_VISIBLE; + // stop cinematics playing in the window + if (item->window.cinematic >= 0) { + DC->stopCinematic(item->window.cinematic); + item->window.cinematic = -1; + } + } + } + } +} + +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; + } + } + } +} + +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; +} + +void Menus_ShowByName(const char *p) { + menuDef_t *menu = Menus_FindByName(p); + if (menu) { + Menus_Activate(menu); + } +} + +void Menus_OpenByName(const char *p) { + Menus_ActivateByName(p); +} + +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); + } +} + +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); +} + +int FPMessageTime = 0; + +void Menus_CloseAll() +{ + int i; + + g_waitingForKey = qfalse; + + for (i = 0; i < menuCount; i++) + { + Menu_RunCloseScript ( &Menus[i] ); + Menus[i].window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); + } + + // Clear the menu stack + openMenuCount = 0; + + FPMessageTime = 0; +} + +qboolean Script_Show(itemDef_t *item, char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menu_ShowItemByName((menuDef_t *) item->parent, name, qtrue); + } + return qtrue; +} + +qboolean Script_Hide(itemDef_t *item, char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menu_ShowItemByName((menuDef_t *) item->parent, name, qfalse); + } + return qtrue; +} + +qboolean Script_FadeIn(itemDef_t *item, char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menu_FadeItemByName((menuDef_t *) item->parent, name, qfalse); + } + + return qtrue; +} + +qboolean Script_FadeOut(itemDef_t *item, char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menu_FadeItemByName((menuDef_t *) item->parent, name, qtrue); + } + return qtrue; +} + +qboolean Script_Open(itemDef_t *item, char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + Menus_OpenByName(name); + } + return qtrue; +} + +qboolean Script_Close(itemDef_t *item, char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + if (Q_stricmp(name, "all") == 0) + { + Menus_CloseAll(); + } + else + { + Menus_CloseByName(name); + } + } + return qtrue; +} + +//void Menu_TransitionItemByName(menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, int time, float amt) +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_Transition3ItemByName +================= +*/ +//JLF +#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 + + + + + + + +#define MAX_DEFERRED_SCRIPT 2048 + +char ui_deferredScript [ MAX_DEFERRED_SCRIPT ]; +itemDef_t* ui_deferredScriptItem = NULL; + +/* +================= +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, char **args ) +{ + // Should the script be deferred? + if ( DC->deferScript ( (char**)args ) ) + { + // Need the item the script was being run on + ui_deferredScriptItem = item; + + // Save the rest of the script + Q_strncpyz ( ui_deferredScript, *args, MAX_DEFERRED_SCRIPT ); + + // 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, char **args ) +{ + // Make sure there is something to run. + if ( !ui_deferredScript[0] || !ui_deferredScriptItem ) + { + return qtrue; + } + + // Run the deferred script now + Item_RunScript ( ui_deferredScriptItem, ui_deferredScript ); + + return qtrue; +} + +qboolean Script_Transition(itemDef_t *item, char **args) +{ + const char *name; + rectDef_t rectFrom, rectTo; + int time; + float amt; + + if (String_Parse(args, &name)) + { + if ( Rect_Parse(args, &rectFrom) && Rect_Parse(args, &rectTo) && Int_Parse(args, &time) && Float_Parse(args, &amt)) + { + Menu_TransitionItemByName((menuDef_t *) item->parent, name, &rectFrom, &rectTo, time, amt); + } + } + + return qtrue; +} + +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); + } + } +} + +void Menu_ItemDisable(menuDef_t *menu, char *name,int disableFlag) +{ + int j,count; + itemDef_t *itemFound; + + count = Menu_ItemsMatchingGroup(menu, name); + // Loop through all items that have this name + for (j = 0; j < count; j++) + { + itemFound = Menu_GetMatchingItemByNumber( menu, j, name); + if (itemFound != NULL) + { + itemFound->disabled = disableFlag; + // Just in case it had focus + itemFound->window.flags &= ~WINDOW_MOUSEOVER; + } + } +} + +// Set item disable flags +qboolean Script_Disable(itemDef_t *item, char **args) +{ + char *name; + int value; + menuDef_t *menu; + + if (String_Parse(args, (const char **)&name)) + { + char buff[1024]; + + // Is is specifying a cvar to get the item name from? + if (name[0] == '*') + { + name += 1; + DC->getCVarString(name, buff, sizeof(buff)); + name = buff; + } + + if ( Int_Parse(args, &value)) + { + menu = Menu_GetFocused(); + Menu_ItemDisable(menu, name,value); + } + } + + return qtrue; +} + + +// Scale the given item instantly. +qboolean Script_Scale(itemDef_t *item, char **args) +{ + const char *name; + float scale; + int j,count; + itemDef_t *itemFound; + rectDef_t rectTo; + + if (String_Parse(args, &name)) + { + char buff[1024]; + + // Is is specifying a cvar to get the item name from? + if (name[0] == '*') + { + name += 1; + DC->getCVarString(name, buff, sizeof(buff)); + name = buff; + } + + count = Menu_ItemsMatchingGroup((menuDef_t *) item->parent, name); + + if ( Float_Parse(args, &scale)) + { + for (j = 0; j < count; j++) + { + itemFound = Menu_GetMatchingItemByNumber( (menuDef_t *) item->parent, j, name); + if (itemFound != NULL) + { + rectTo.h = itemFound->window.rect.h * scale; + rectTo.w = itemFound->window.rect.w * scale; + + rectTo.x = itemFound->window.rect.x + ((itemFound->window.rect.h - rectTo.h)/2); + rectTo.y = itemFound->window.rect.y + ((itemFound->window.rect.w - rectTo.w)/2); + + Menu_TransitionItemByName((menuDef_t *) item->parent, name, 0, &rectTo, 1, 1); + } + } + } + } + + return qtrue; +} + +qboolean Script_Orbit(itemDef_t *item, 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) ) + { + Menu_OrbitItemByName((menuDef_t *) item->parent, name, x, y, cx, cy, time); + } + } + + return qtrue; +} + +qboolean Script_SetFocus(itemDef_t *item, char **args) +{ + const char *name; + itemDef_t *focusItem; + + if (String_Parse(args, &name)) { + focusItem = Menu_FindItemByName((menuDef_t *) item->parent, name); + 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 ); + } + } + } + + return qtrue; +} + +qboolean Script_SetPlayerModel(itemDef_t *item, char **args) +{ + const char *name; + if (String_Parse(args, &name)) + { + DC->setCVar("model", name); + } + + return qtrue; +} + +/* +================= +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; +} +/* +================= +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, char **args) +{ + const char *name; + rectDef_t rectTo; + int time; + float amt; + + if (String_Parse(args, &name)) + { + if ( ParseRect((const char **) args, &rectTo) && Int_Parse(args, &time) && !COM_ParseFloat((const char **) 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 +================= +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, 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 + + + + + + + + + + + + + + + + + + + + + + + +qboolean Script_SetCvar(itemDef_t *item, char **args) +{ + const char *cvar, *val; + if (String_Parse(args, &cvar) && String_Parse(args, &val)) + { + DC->setCVar(cvar, val); + } + return qtrue; +} + +qboolean Script_SetCvarToCvar(itemDef_t *item, char **args) { + const char *cvar, *val; + if (String_Parse(args, &cvar) && String_Parse(args, &val)) { + char cvarBuf[1024]; + DC->getCVarString(val, cvarBuf, sizeof(cvarBuf)); + DC->setCVar(cvar, cvarBuf); + } + return qtrue; +} + +qboolean Script_Exec(itemDef_t *item, char **args) { + const char *val; + if (String_Parse(args, &val)) { + DC->executeText(EXEC_APPEND, va("%s ; ", val)); + } + return qtrue; +} + +qboolean Script_Play(itemDef_t *item, char **args) { + const char *val; + if (String_Parse(args, &val)) { + DC->startLocalSound(DC->registerSound(val), CHAN_AUTO); + } + return qtrue; +} + +qboolean Script_playLooped(itemDef_t *item, char **args) { + const char *val; + if (String_Parse(args, &val)) { + DC->stopBackgroundTrack(); + DC->startBackgroundTrack(val, val, qfalse); + } + return qtrue; +} + + +commandDef_t commandList[] = +{ + {"fadein", &Script_FadeIn}, // group/name + {"fadeout", &Script_FadeOut}, // group/name + {"show", &Script_Show}, // group/name + {"hide", &Script_Hide}, // group/name + {"setcolor", &Script_SetColor}, // works on this + {"open", &Script_Open}, // nenu + {"close", &Script_Close}, // menu + {"setasset", &Script_SetAsset}, // works on this + {"setbackground", &Script_SetBackground}, // works on this + {"setitemrectcvar", &Script_SetItemRectCvar}, // group/name + {"setitembackground", &Script_SetItemBackground},// group/name + {"setitemtext", &Script_SetItemText}, // group/name + {"setitemcolor", &Script_SetItemColor}, // group/name + {"setitemcolorcvar", &Script_SetItemColorCvar},// group/name + {"setitemrect", &Script_SetItemRect}, // group/name + {"setteamcolor", &Script_SetTeamColor}, // sets this background color to team color + {"setfocus", &Script_SetFocus}, // sets focus + {"setplayermodel", &Script_SetPlayerModel}, // sets model + {"transition", &Script_Transition}, // group/name + {"setcvar", &Script_SetCvar}, // name + {"setcvartocvar", &Script_SetCvarToCvar}, // name + {"exec", &Script_Exec}, // group/name + {"play", &Script_Play}, // group/name + {"playlooped", &Script_playLooped}, // group/name + {"orbit", &Script_Orbit}, // group/name + {"scale", &Script_Scale}, // group/name + {"disable", &Script_Disable}, // group/name + {"defer", &Script_Defer}, // + {"rundeferred", &Script_RunDeferred}, // + {"transition2", &Script_Transition2}, // group/name +}; + +// 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); + } + } +} + +// 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->text = NULL; // Null this out because this would take presidence over cvar text. + 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 + { + item->text = text; + if ( item->type == ITEM_TYPE_TEXTSCROLL ) + { + textScrollDef_t *scrollPtr = (textScrollDef_t*)item->typeData; + if ( scrollPtr ) + { + scrollPtr->startPos = 0; + scrollPtr->endPos = 0; + } + + Item_TextScroll_BuildLines ( item ); + } + } + } + } +} + +int scriptCommandCount = sizeof(commandList) / sizeof(commandDef_t); + +void Item_RunScript(itemDef_t *item, const char *s) +{ + char script[2048], *p; + int i; + qboolean bRan; + + script[0] = 0; + + if (item && s && s[0]) + { + Q_strcat(script, 2048, s); + p = script; + + 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) + { + // Allow a script command to stop processing the script + if ( !commandList[i].handler(item, &p) ) + { + return; + } + + bRan = qtrue; + break; + } + } + + // not in our auto list, pass to handler + if (!bRan) + { + DC->runScript(&p); + } + } + } +} + + +qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag) { + char script[2048], *p; + if (item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest) { + char buff[2048]; + DC->getCVarString(item->cvarTest, buff, sizeof(buff)); + + Q_strncpyz(script, item->enableCvar, 2048); + 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; +} + + +// 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; + menuDef_t *parent; // bk001206: = (menuDef_t*)item->parent; + // 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)) { + return qfalse; + } + + // bk001206 - this can be NULL. + parent = (menuDef_t*)item->parent; + + // items can be enabled and disabled + if (item->disabled) + { + return qfalse; + } + + // 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); + + if (item->type == ITEM_TYPE_TEXT) { + rectDef_t r; + r = item->textRect; + r.y -= r.h; + +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&r, x, y)) +#endif + { + item->window.flags |= WINDOW_HASFOCUS; + if (item->focusSound) { + sfx = &item->focusSound; + } + playSound = qtrue; + } +#ifndef _XBOX + else +#endif + { + if (oldFocus) { + oldFocus->window.flags |= WINDOW_HASFOCUS; + if (oldFocus->onFocus) { + Item_RunScript(oldFocus, oldFocus->onFocus); + } + } + } + } else { + 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; +} + +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; + + 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); +} + +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; +} + +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; +} + +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; + } +} + +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); + } +} + +float Item_Slider_ThumbPosition(itemDef_t *item) { + float value, range, x; + editFieldDef_t *editDef = (editFieldDef_t *) item->typeData; + + if (item->text) { + x = item->textRect.x + item->textRect.w + 8; + } else { + x = item->window.rect.x; + } + + if (editDef == NULL && item->cvar) { + return x; + } + + 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; + value /= range; + //value /= (editDef->maxVal - editDef->minVal); + value *= SLIDER_WIDTH; + x += value; + // vm fuckage + //x = x + (((float)value / editDef->maxVal) * SLIDER_WIDTH); + return x; +} + +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; +} + +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; + } + } + // Vertical Scroll + else + { + // Multiple rows and columns (since it's more than twice as wide as an element) + if (( item->window.rect.w > (listPtr->elementWidth*2)) && (listPtr->elementStyle == LISTBOX_IMAGE)) + { + 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_PGUP; + } + + r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_PGDN; + } + + thumbstart = Item_ListBox_ThumbPosition(item); + r.y = thumbstart; + if (Rect_ContainsPoint(&r, x, y)) + { + return WINDOW_LB_THUMB; + } + + } + 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; +} + + +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.. + } + } + } + // Window Vertical Scroll + else if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) + { + // Calc which element the mouse is over + 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)) + { + // Multiple rows and columns (since it's more than twice as wide as an element) + if (( item->window.rect.w > (listPtr->elementWidth*2)) && (listPtr->elementStyle == LISTBOX_IMAGE)) + { + int row,column,rowLength; + + row = (int)((y - 2 - r.y) / listPtr->elementHeight); + rowLength = (int) r.w / listPtr->elementWidth; + column = (int)((x - r.x) / listPtr->elementWidth); + + listPtr->cursorPos = (row * rowLength)+column + listPtr->startPos; + if (listPtr->cursorPos >= listPtr->endPos) + { + listPtr->cursorPos = listPtr->endPos; + } + } + // single column + else + { + listPtr->cursorPos = (int)((y - 2 - r.y) / listPtr->elementHeight) + listPtr->startPos; + if (listPtr->cursorPos > listPtr->endPos) + { + listPtr->cursorPos = listPtr->endPos; + } + } + } + } +} + +void Item_MouseEnter(itemDef_t *item, float x, float y) { + + rectDef_t r; + if (item) { + r = item->textRect; + r.y -= r.h; + // in the text rect? + + // items can be enabled and disabled + if (item->disabled) + { + return; + } + + // 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 ); + } + } + } +} + +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); + } +} + +itemDef_t *Menu_HitTest(menuDef_t *menu, float x, float y) { + int i; + for (i = 0; i < menu->itemCount; i++) { + if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) { + return menu->items[i]; + } + } + return NULL; +} + +void Item_SetMouseOver(itemDef_t *item, qboolean focus) { + if (item) { + if (focus) { + item->window.flags |= WINDOW_MOUSEOVER; + } else { + item->window.flags &= ~WINDOW_MOUSEOVER; + } + } +} + + +qboolean Item_OwnerDraw_HandleKey(itemDef_t *item, int key) { + if (item && DC->ownerDrawHandleKey) + { + + // yep this is an ugly hack + if( key == A_MOUSE1 || key == A_MOUSE2 ) + { + switch( item->window.ownerDraw ) + { + case UI_FORCE_SIDE: + case UI_FORCE_RANK_HEAL: + case UI_FORCE_RANK_LEVITATION: + case UI_FORCE_RANK_SPEED: + case UI_FORCE_RANK_PUSH: + case UI_FORCE_RANK_PULL: + case UI_FORCE_RANK_TELEPATHY: + case UI_FORCE_RANK_GRIP: + case UI_FORCE_RANK_LIGHTNING: + case UI_FORCE_RANK_RAGE: + case UI_FORCE_RANK_PROTECT: + case UI_FORCE_RANK_ABSORB: + case UI_FORCE_RANK_TEAM_HEAL: + case UI_FORCE_RANK_TEAM_FORCE: + case UI_FORCE_RANK_DRAIN: + case UI_FORCE_RANK_SEE: + case UI_FORCE_RANK_SABERATTACK: + case UI_FORCE_RANK_SABERDEFEND: + case UI_FORCE_RANK_SABERTHROW: + if(!Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) ) + { + return qfalse; + } + break; + } + } + + + return DC->ownerDrawHandleKey(item->window.ownerDraw, item->window.ownerDrawFlags, &item->special, key); + } + return qfalse; +} + + +#ifdef _XBOX +// Xbox-only key handlers + +//JLF new func +qboolean Item_Button_HandleKey(itemDef_t *item, int key) +{ + 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_Text_HandleKey +================= +*/ +qboolean Item_Text_HandleKey(itemDef_t *item, int key) +{ +//JLFSELECTIONRightLeft + 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; +} + +#endif // _XBOX + + +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, viewmax; +//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 = (item->window.rect.w / listPtr->elementWidth); + if ( key == A_CURSOR_LEFT || key == A_KP_4 ) + { + if (!listPtr->notselectable) { + listPtr->cursorPos--; +#ifdef _XBOX + listPtr->startPos--; +#endif + if (listPtr->cursorPos < 0) { + listPtr->cursorPos = 0; + return qfalse; + } + if (listPtr->cursorPos < listPtr->startPos) { + listPtr->startPos = listPtr->cursorPos; +//JLF +#ifndef _XBOX + return qfalse; +#endif + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, NULL); + } + else { + listPtr->startPos--; + if (listPtr->startPos < 0) + listPtr->startPos = 0; + } + 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; +//JLF +#ifndef _XBOX + return qfalse; +#endif + } + if (listPtr->cursorPos >= count) { + listPtr->cursorPos = count-1; + return qfalse; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, NULL); + } + else { + listPtr->startPos++; + if (listPtr->startPos >= count) + listPtr->startPos = count-1; + } + return qtrue; + } + } + // Vertical scroll + else { + + // Multiple rows and columns (since it's more than twice as wide as an element) + if (( item->window.rect.w > (listPtr->elementWidth*2)) && (listPtr->elementStyle == LISTBOX_IMAGE)) + { + viewmax = (item->window.rect.w / listPtr->elementWidth); + } + else + { + viewmax = (item->window.rect.h / listPtr->elementHeight); + } + + if ( key == A_CURSOR_UP || key == A_KP_8 ) + { + if (!listPtr->notselectable) { + listPtr->cursorPos--; + if (listPtr->cursorPos < 0) { + listPtr->cursorPos = 0; + return qfalse; + } + if (listPtr->cursorPos < listPtr->startPos) { + listPtr->startPos = listPtr->cursorPos; +//JLF +#ifndef _XBOX + return qfalse; +#endif + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, NULL); + } + else { + listPtr->startPos--; + if (listPtr->startPos < 0) + listPtr->startPos = 0; + } + return qtrue; + } + if ( key == A_CURSOR_DOWN || key == A_KP_2 ) + { + if (!listPtr->notselectable) { + listPtr->cursorPos++; + if (listPtr->cursorPos < listPtr->startPos) { + listPtr->startPos = listPtr->cursorPos; +//JLF +#ifndef _XBOX + return qfalse; +#endif + } + if (listPtr->cursorPos >= count) { + listPtr->cursorPos = count-1; + return qfalse; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, NULL); + } + else { + listPtr->startPos++; + if (listPtr->startPos > max) + listPtr->startPos = max; + } + return qtrue; + } + } + // mouse hit + if (key == A_MOUSE1 || key == A_MOUSE2) { + if (item->window.flags & WINDOW_LB_LEFTARROW) { + listPtr->startPos--; + if (listPtr->startPos < 0) { + listPtr->startPos = 0; + } + } else if (item->window.flags & WINDOW_LB_RIGHTARROW) { + // one down + listPtr->startPos++; + if (listPtr->startPos > max) { + listPtr->startPos = max; + } + } else if (item->window.flags & WINDOW_LB_PGUP) { + // page up + listPtr->startPos -= viewmax; + if (listPtr->startPos < 0) { + listPtr->startPos = 0; + } + } else if (item->window.flags & WINDOW_LB_PGDN) { + // page down + listPtr->startPos += viewmax; + if (listPtr->startPos > (max)) { + listPtr->startPos = (max); + } + } else if (item->window.flags & WINDOW_LB_THUMB) { + // Display_SetCaptureItem(item); + } else { + // select an item +#ifdef _XBOX + if (listPtr->doubleClick) { +#else + if (DC->realTime < lastListBoxClickTime && listPtr->doubleClick) { +#endif + Item_RunScript(item, listPtr->doubleClick); + } + lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY; +// if (item->cursorPos != listPtr->cursorPos) + { + int prePos = item->cursorPos; + + item->cursorPos = listPtr->cursorPos; + + if (!DC->feederSelection(item->special, item->cursorPos, item)) + { + item->cursorPos = listPtr->cursorPos = prePos; + } + } + } + return qtrue; + } + if ( key == A_HOME || key == A_KP_7) { + // home + listPtr->startPos = 0; + return qtrue; + } + if ( key == A_END || key == A_KP_1) { + // end + listPtr->startPos = max; + return qtrue; + } + if (key == A_PAGE_UP || key == A_KP_9 ) { + // page up + if (!listPtr->notselectable) { + listPtr->cursorPos -= viewmax; + if (listPtr->cursorPos < 0) { + listPtr->cursorPos = 0; + } + if (listPtr->cursorPos < listPtr->startPos) { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, NULL); + } + else { + listPtr->startPos -= viewmax; + if (listPtr->startPos < 0) { + listPtr->startPos = 0; + } + } + return qtrue; + } + if ( key == A_PAGE_DOWN || key == A_KP_3 ) { + // page down + if (!listPtr->notselectable) { + listPtr->cursorPos += viewmax; + if (listPtr->cursorPos < listPtr->startPos) { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= count) { + listPtr->cursorPos = count-1; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, NULL); + } + else { + listPtr->startPos += viewmax; + if (listPtr->startPos > max) { + listPtr->startPos = max; + } + } + return qtrue; + } + } + return qfalse; +} + +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; + +} + +int Item_Multi_CountSettings(itemDef_t *item) { + multiDef_t *multiPtr = (multiDef_t*)item->typeData; + if (multiPtr == NULL) { + return 0; + } + return multiPtr->count; +} + +int Item_Multi_FindCvarByValue(itemDef_t *item) { + char buff[2048]; + 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; +} + +const char *Item_Multi_Setting(itemDef_t *item) { + char buff[2048]; + 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 + { + if (item->cvar) // Was a cvar given? + { + value = DC->getCVarValue(item->cvar); + } + } + + 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 ""; +} + +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 + { + int current = Item_Multi_FindCvarByValue(item); + int max = Item_Multi_CountSettings(item); + + if (key == A_MOUSE2 || key == A_CURSOR_LEFT) // Xbox uses CURSOR_LEFT + { + 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); + } + + return qtrue; + } + } + } + return qfalse; +} + +// Leaving an edit field so reset it so it prints from the beginning. +void Leaving_EditField(itemDef_t *item) +{ + // switching fields so reset printed text of edit field + if ((g_editingField==qtrue) && (item->type==ITEM_TYPE_EDITFIELD)) + { + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + if (editPtr) + { + editPtr->paintOffset = 0; + } + } +} + +qboolean Item_TextField_HandleKey(itemDef_t *item, int key) { + char buff[2048]; + int len; + itemDef_t *newItem = NULL; + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + + if (item->cvar) { + + buff[0] = 0; + 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; + + //rww - nul-terminate! + if (item->cursorPos+1 < 2048) + { + buff[item->cursorPos+1] = 0; + } + else + { + buff[item->cursorPos] = 0; + } + + 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) {// || ( tolower(key) == 'a' && trap_Key_IsDown( K_CTRL ) ) ) { + item->cursorPos = 0; + editPtr->paintOffset = 0; + return qtrue; + } + + if ( key == A_END || key == A_KP_1) {// ( tolower(key) == 'e' && trap_Key_IsDown( K_CTRL ) ) ) { + 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) + { + // switching fields so reset printed text of edit field + Leaving_EditField(item); + g_editingField = qfalse; + newItem = Menu_SetNextCursorItem((menuDef_t *) item->parent); + if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) + { + g_editItem = newItem; + g_editingField = qtrue; + } + } + + if (key == A_CURSOR_UP || key == A_KP_8) + { + // switching fields so reset printed text of edit field + Leaving_EditField(item); + + g_editingField = qfalse; + newItem = Menu_SetPrevCursorItem((menuDef_t *) item->parent); + if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) + { + g_editItem = newItem; + g_editingField = qtrue; + } + } + + 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; + } + } +} + +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; + } + } +} + +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); + + // Multiple rows and columns (since it's more than twice as wide as an element) + if (( si->item->window.rect.w > (listPtr->elementWidth*2)) && (listPtr->elementStyle == LISTBOX_IMAGE)) + { + int rowLength, rowMax; + rowLength = si->item->window.rect.w / listPtr->elementWidth; + rowMax = max / rowLength; + + pos = (DC->cursory - r.y - SCROLLBAR_SIZE/2) * rowMax / (r.h - SCROLLBAR_SIZE); + pos *= rowLength; + } + else + { + 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; + } + } +} + +static void Scroll_Slider_ThumbFunc(void *p) { + float x, value, cursorx; + scrollInfo_t *si = (scrollInfo_t*)p; + editFieldDef_t *editDef = (editFieldDef_t *) 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)); +} + +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; + } + } +} + +void Item_StopCapture(itemDef_t *item) { + +} + +qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) { + float x, value, width, work; + + //DC->Print("slider handle key\n"); +//JLF MPMOVED +#ifndef _XBOX + if (item->window.flags & WINDOW_HASFOCUS && item->cvar && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) { + if (key == A_MOUSE1 || key == A_ENTER || key == A_MOUSE2 || key == A_MOUSE3) { + editFieldDef_t *editDef = (editFieldDef_t *) item->typeData; + if (editDef) { + rectDef_t testRect; + width = SLIDER_WIDTH; + if (item->text) { + x = item->textRect.x + item->textRect.w + 8; + } else { + x = item->window.rect.x; + } + + testRect = item->window.rect; + testRect.x = x; + value = (float)SLIDER_THUMB_WIDTH / 2; + testRect.x -= value; + //DC->Print("slider x: %f\n", testRect.x); + testRect.w = (SLIDER_WIDTH + (float)SLIDER_THUMB_WIDTH / 2); + //DC->Print("slider w: %f\n", testRect.w); + if (Rect_ContainsPoint(&testRect, DC->cursorx, DC->cursory)) { + work = DC->cursorx - x; + value = work / width; + value *= (editDef->maxVal - editDef->minVal); + // vm fuckage + // value = (((float)(DC->cursorx - x)/ SLIDER_WIDTH) * (editDef->maxVal - editDef->minVal)); + value += editDef->minVal; + DC->setCVar(item->cvar, va("%f", value)); + return qtrue; + } + } + } + } +#else //def _XBOX +//JLF + 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; + } + } + } +#endif + DC->Print("slider handle key exit\n"); + return qfalse; +} + + +qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) { + + if (itemCapture) { + Item_StopCapture(itemCapture); + itemCapture = NULL; + captureFunc = 0; + captureData = NULL; + } else { + // bk001206 - parentheses + 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: + if (key == A_MOUSE1 || key == A_MOUSE2 || key == A_ENTER) + { + editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData; + + if (item->cvar && editPtr) + { + editPtr->paintOffset = 0; + } + + //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; +#ifdef _XBOX + case ITEM_TYPE_TEXT: + return Item_Text_HandleKey(item, key); + break; +#endif + //case ITEM_TYPE_IMAGE: + // Item_Image_Paint(item); + // break; + default: + return qfalse; + break; + } + + //return qfalse; +} + + +//JLFACCEPT MPMOVED +/* +----------------------------------------- +Item_HandleAccept + If Item has an accept script, run it. +------------------------------------------- +*/ +qboolean Item_HandleAccept(itemDef_t * item) +{ + if (item->accept) + { + Item_RunScript(item, item->accept); + return qtrue; + } + return qfalse; +} + +#ifdef _XBOX // Xbox-only event handlers + +//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 qtrue; + } + return qfalse; +} + +//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 qtrue; + } + return qfalse; +} + +//JLF END +#endif // _XBOX + + +void Item_Action(itemDef_t *item) { + if (item) { + Item_RunScript(item, item->action); + } +} + + + +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; + +} + +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; +} + +static void Window_CloseCinematic(windowDef_t *window) { + if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0) { + DC->stopCinematic(window->cinematic); + window->cinematic = -1; + } +} + +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); + } + } + } +} + +static void Display_CloseCinematics() { + int i; + for (i = 0; i < menuCount; i++) { + Menu_CloseCinematics(&Menus[i]); + } +} + +void Menus_Activate(menuDef_t *menu) { + +//JLFCALLOUT MPMOVED +#ifdef _XBOX + DC->setCVar("ui_hideAcallout" ,"0"); + DC->setCVar("ui_hideBcallout" ,"0"); + DC->setCVar("ui_hideCcallout" ,"0"); +#endif +//JLF END + menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE); + if (menu->onOpen) { + itemDef_t item; + item.parent = menu; + Item_RunScript(&item, menu->onOpen); + } + + if (menu->soundName && *menu->soundName) { +// DC->stopBackgroundTrack(); // you don't want to do this since it will reset s_rawend + DC->startBackgroundTrack(menu->soundName, menu->soundName, qfalse); + } + + menu->appearanceTime = 0; + Display_CloseCinematics(); + +} + +int Display_VisibleMenuCount() { + int i, count; + count = 0; + for (i = 0; i < menuCount; i++) { + if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE)) { + count++; + } + } + return count; +} + +void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) { + if (menu) { +//JLFMOUSE +#ifdef _XBOX + Menu_HandleMouseMove(menu, DC->cursorx, DC->cursory); + Menu_HandleKey(menu, key, down); + return; +#endif + int i; + // basically the behaviour we are looking for is if there are windows in the stack.. see if + // the cursor is within any of them.. if not close them otherwise activate them and pass the + // key on.. force a mouse move to activate focus and script stuff + if (down && menu->window.flags & WINDOW_OOB_CLICK) { + Menu_RunCloseScript(menu); + menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); + } + + for (i = 0; i < menuCount; i++) { + if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) { + Menu_RunCloseScript(menu); + menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); + Menus_Activate(&Menus[i]); + Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory); + Menu_HandleKey(&Menus[i], key, down); + } + } + + if (Display_VisibleMenuCount() == 0) { + if (DC->Pause) { + DC->Pause(qfalse); + } + } + Display_CloseCinematics(); + } +} + + +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) + { + // switching fields so reset printed text of edit field + Leaving_EditField(g_editItem); + 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; + } + //JLFMOUSE + // see if the mouse is within the window bounds and if so is this a mouse click +#ifndef _XBOX + if (down && !(menu->window.flags & WINDOW_POPUP) && !Rect_ContainsPoint(&menu->window.rect, DC->cursorx, DC->cursory)) +#else + if (down) +#endif + { + static qboolean inHandleKey = qfalse; + // bk001206 - parentheses + if (!inHandleKey && ( key == A_MOUSE1 || key == A_MOUSE2 || key == A_MOUSE3 ) ) { + inHandleKey = qtrue; + Menus_HandleOOBClick(menu, key, down); + inHandleKey = qfalse; + inHandler = qfalse; + return; + } + } + + // get the item with focus + for (i = 0; i < menu->itemCount; i++) { + if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { + item = menu->items[i]; + } + } + + // Ignore if disabled + if (item && item->disabled) + { + return; + } + + if (item != NULL) { + if (Item_HandleKey(item, key, down)) + { + +#ifdef _XBOX + if (key == A_MOUSE1 || key == A_MOUSE2 ||(item->type == ITEM_TYPE_MULTI && (key == A_CURSOR_RIGHT || key == A_CURSOR_LEFT))) + { +#endif + // It is possible for an item to be disable after Item_HandleKey is run (like in Voice Chat) + if (!item->disabled) + { + Item_Action(item); + } +#ifdef _XBOX + } +#endif + inHandler = qfalse; + return; + } + } + + if (!down) { + inHandler = qfalse; + return; + } + + // default handling + switch ( key ) { + + case A_F11: + if (DC->getCVarValue("developer")) { + debugMode ^= 1; + } + break; + + case A_F12: + if (DC->getCVarValue("developer")) { + DC->executeText(EXEC_APPEND, "screenshot\n"); + } + break; + case A_KP_8: + case A_CURSOR_UP: + Menu_SetPrevCursorItem(menu); + break; + + case A_ESCAPE: + if (!g_waitingForKey && menu->onESC) { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->onESC); + } + g_waitingForKey = qfalse; + break; + case A_TAB: + case A_KP_2: + case A_CURSOR_DOWN: + Menu_SetNextCursorItem(menu); + break; + + case A_MOUSE1: + case A_MOUSE2: + if (item) { + if (item->type == ITEM_TYPE_TEXT) { +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) +#endif + { + Item_Action(item); + } + } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) { +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) +#endif + { + Item_Action(item); + item->cursorPos = 0; + g_editingField = qtrue; + g_editItem = item; + DC->setOverstrikeMode(qtrue); + } + } + + //JLFACCEPT +// add new types here as needed +/* Notes: + Most controls will use the dpad to move through the selection possibilies. Buttons are the only exception. + Buttons will be assumed to all be on one menu together. If the start or A button is pressed on a control focus, that + means that the menu is accepted and move onto the next menu. If the start or A button is pressed on a button focus it + should just process the action and not support the accept functionality. +*/ + +//JLFACCEPT MPMOVED + else if ( item->type == ITEM_TYPE_MULTI || item->type == ITEM_TYPE_YESNO || item->type == ITEM_TYPE_SLIDER) + { + if (Item_HandleAccept(item)) + { + //Item processed it overriding the menu processing + return; + } + else if (menu->onAccept) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->onAccept); + } + } +//END JLFACCEPT + else { +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) +#endif + { + + Item_Action(item); + } + } + } + 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) { + if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) { + item->cursorPos = 0; + g_editingField = qtrue; + g_editItem = item; + DC->setOverstrikeMode(qtrue); + } else { + Item_Action(item); + } + } + break; + } + inHandler = qfalse; +} + +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; +} + +void Rect_ToWindowCoords(rectDef_t *rect, windowDef_t *window) { + ToWindowCoords(&rect->x, &rect->y, window); +} + +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) +#ifndef CGAME + || (item->text && item->text[0]=='@' && item->asset != se_language.modificationCount ) //string package language changed +#endif + ) + { + int originalWidth = DC->textWidth(textPtr, item->textscale, item->iMenuFont); + + 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->iMenuFont); + } + + *width = DC->textWidth(textPtr, item->textscale, item->iMenuFont); + *height = DC->textHeight(textPtr, item->textscale, item->iMenuFont); + + 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); +#ifndef CGAME + if (item->text && item->text[0]=='@' )//string package + {//mark language + item->asset = se_language.modificationCount; + } +#endif + } +} + +void Item_TextColor(itemDef_t *item, vec4_t *newColor) { + vec4_t 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); + + 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 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,*newColor,0.5+0.5*sin((float)(DC->realTime / PULSE_DIVISOR))); + } else { + memcpy(newColor, &item->window.foreColor, sizeof(vec4_t)); + // items can be enabled and disabled based on cvars + } + + if (item->disabled) + { + memcpy(newColor, &parent->disableColor, sizeof(vec4_t)); + } + + 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)); + } + } +} + +void Item_Text_AutoWrapped_Paint(itemDef_t *item) { + char text[2048]; + const char *p, *textPtr, *newLinePtr; + char buff[2048]; + int height, len, textWidth, newLine, newLineWidth; //int width; + 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 + { + trap_SP_GetStringTextString( &textPtr[1], text, sizeof(text)); + textPtr = text; + } + 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->iMenuFont); + + y = item->textaligny; + len = 0; + buff[0] = '\0'; + newLine = 0; + newLineWidth = 0; + p = textPtr; + while (p) { //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) || *p == '\n' || *p == '\0') { + 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'; + DC->drawText(item->textRect.x, item->textRect.y, item->textscale, color, buff, 0, 0, item->textStyle, item->iMenuFont); + } + if (*p == '\0') { + break; + } + // + y += height + 5; + p = newLinePtr; + len = 0; + newLine = 0; + newLineWidth = 0; + continue; + } + buff[len++] = *p++; + buff[len] = '\0'; + } +} + +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 + { + trap_SP_GetStringTextString( &textPtr[1], text, sizeof(text)); + textPtr = text; + } + 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, 0, item->textStyle, item->iMenuFont); + y += height + 2; + start += p - start + 1; + p = strchr(p+1, '\r'); + } + DC->drawText(x, y, item->textscale, color, start, 0, 0, item->textStyle, item->iMenuFont); +} + +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 + { + trap_SP_GetStringTextString( &textPtr[1], text, sizeof(text)); + textPtr = text; + } + + // 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, 0, item->textStyle, item->iMenuFont); + + if (item->text2) // Is there a second line of text? + { + textPtr = item->text2; + if (*textPtr == '@') // string reference + { + trap_SP_GetStringTextString( &textPtr[1], text, sizeof(text)); + textPtr = text; + } + Item_TextColor(item, &color); + DC->drawText(item->textRect.x + item->text2alignx, item->textRect.y + item->text2aligny, item->textscale, color, textPtr, 0, 0, item->textStyle,item->iMenuFont); + } +} + + + +//float trap_Cvar_VariableValue( const char *var_name ); +//void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +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)); + if (buff[0] == '@') // string reference + { + trap_SP_GetStringTextString( &buff[1], 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 = (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, item->window.rect.w, item->textStyle, item->iMenuFont); + } else { + DC->drawText(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, 0, item->window.rect.w, item->textStyle,item->iMenuFont); + } +} + +void Item_YesNo_Paint(itemDef_t *item) { + char sYES[20]; + char sNO[20]; + vec4_t newColor, lowLight; + float value; + menuDef_t *parent = (menuDef_t*)item->parent; + const char *yesnovalue; + + value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + + 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)); + } + + + trap_SP_GetStringTextString("MENUS_YES",sYES, sizeof(sYES)); + trap_SP_GetStringTextString("MENUS_NO", sNO, sizeof(sNO)); + +//JLFYESNO MPMOVED + if (item->invertYesNo) + yesnovalue = (value == 0) ? sYES : sNO; + else + yesnovalue = (value != 0) ? sYES : sNO; + + if (item->text) + { + Item_Text_Paint(item); +//JLF MPMOVED +#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, 0,item->textStyle, item->iMenuFont); + else +#endif + DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, yesnovalue, 0, 0, item->textStyle, item->iMenuFont); + + } + else + { +//JLF MPMOVED +#ifdef _XBOX + DC->drawText(item->textRect.x + item->xoffset, item->textRect.y, item->textscale, newColor, yesnovalue , 0, 0, item->textStyle, item->iMenuFont); +#else + DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, yesnovalue , 0, 0, item->textStyle, item->iMenuFont); +#endif + } + +/* JLF ORIGINAL CODE + if (item->text) { + Item_Text_Paint(item); +// DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, (value != 0) ? sYES : sNO, 0, 0, item->textStyle); + DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, (value != 0) ? sYES : sNO, 0, 0, item->textStyle,item->iMenuFont); + } else { +// DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? sYES : sNO, 0, 0, item->textStyle); + DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? sYES : sNO, 0, 0, item->textStyle,item->iMenuFont); + } +*/ +} + +void Item_Multi_Paint(itemDef_t *item) { + vec4_t newColor, lowLight; + const char *text = ""; + menuDef_t *parent = (menuDef_t*)item->parent; + char temp[MAX_STRING_CHARS]; + + 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)); + } + + text = Item_Multi_Setting(item); + if (*text == '@') // string reference + { + trap_SP_GetStringTextString( &text[1] , temp, sizeof(temp)); + text = temp; + } + // Is is specifying a cvar to get the item name from? + else if (*text == '*') + { + DC->getCVarString(&text[1], temp, sizeof(temp)); + text = temp; + } + + if (item->text) { + Item_Text_Paint(item); +//JLF MPMOVED +#ifdef _XBOX + if ( item->xoffset) + DC->drawText(item->textRect.x + item->textRect.w + item->xoffset, item->textRect.y, item->textscale, newColor, text, 0,0, item->textStyle, item->iMenuFont); + else +#endif + DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle,item->iMenuFont); + } else { + //JLF added xoffset + DC->drawText(item->textRect.x+item->xoffset, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle,item->iMenuFont); + } +} + + +typedef struct { + char *command; + int id; + int defaultbind1; + int defaultbind2; + int bind1; + int bind2; +} bind_t; + +typedef struct +{ + char* name; + float defaultvalue; + float value; +} configcvar_t; + + +static bind_t g_bindings[] = +{ + {"+scores", A_TAB, -1, -1, -1}, + {"+button2", A_ENTER, -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", A_SPACE, -1, -1, -1}, + {"+movedown", 'c', -1, -1, -1}, + {"+left", A_CURSOR_LEFT, -1, -1, -1}, + {"+right", A_CURSOR_RIGHT, -1, -1, -1}, + {"+strafe", A_ALT, -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 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}, + {"saberAttackCycle", 'l', -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", -1, -1, -1, -1}, + {"+use", -1, -1, -1, -1}, + {"engage_duel", 'h', -1, -1, -1}, + {"taunt", 'u', -1, -1, -1}, + {"bow", -1, -1, -1, -1}, + {"meditate", -1, -1, -1, -1}, + {"flourish", -1, -1, -1, -1}, + {"gloat", -1, -1, -1, -1}, + {"weapprev", '[', -1, -1, -1}, + {"weapnext", ']', -1, -1, -1}, + {"prevTeamMember", 'w', -1, -1, -1}, + {"nextTeamMember", 'r', -1, -1, -1}, + {"nextOrder", 't', -1, -1, -1}, + {"confirmOrder", 'y', -1, -1, -1}, + {"denyOrder", 'n', -1, -1, -1}, + {"taskOffense", 'o', -1, -1, -1}, + {"taskDefense", 'd', -1, -1, -1}, + {"taskPatrol", 'p', -1, -1, -1}, + {"taskCamp", 'c', -1, -1, -1}, + {"taskFollow", 'f', -1, -1, -1}, + {"taskRetrieve", 'v', -1, -1, -1}, + {"taskEscort", 'e', -1, -1, -1}, + {"taskOwnFlag", 'i', -1, -1, -1}, + {"taskSuicide", 'k', -1, -1, -1}, + {"tauntKillInsult", -1, -1, -1, -1}, + {"tauntPraise", -1, -1, -1, -1}, + {"tauntTaunt", -1, -1, -1, -1}, + {"tauntDeathInsult",-1, -1, -1, -1}, + {"tauntGauntlet", -1, -1, -1, -1}, + {"scoresUp", A_INSERT, -1, -1, -1}, + {"scoresDown", A_DELETE, -1, -1, -1}, + {"messagemode", -1, -1, -1, -1}, + {"messagemode2", -1, -1, -1, -1}, + {"messagemode3", -1, -1, -1, -1}, + {"messagemode4", -1, -1, -1, -1}, + {"+use", -1, -1, -1, -1}, + {"+force_jump", -1, -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}, +//mp only + {"+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_healother", -1, -1, -1, -1}, + {"force_forcepowerother", -1, -1, -1, -1}, + {"force_seeing", -1, -1, -1, -1}, + + {"+useforce", -1, -1, -1, -1}, + {"forcenext", -1, -1, -1, -1}, + {"forceprev", -1, -1, -1, -1}, + {"invnext", -1, -1, -1, -1}, + {"invprev", -1, -1, -1, -1}, + {"use_seeker", -1, -1, -1, -1}, + {"use_field", -1, -1, -1, -1}, + {"use_bacta", -1, -1, -1, -1}, + {"use_electrobinoculars", -1, -1, -1, -1}, + {"use_sentry", -1, -1, -1, -1}, + {"cg_thirdperson !",-1, -1, -1, -1}, + {"automap_button", -1, -1, -1, -1}, + {"automap_toggle", -1, -1, -1, -1}, + {"voicechat", -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]; + } + + //s_controls.invertmouse.curvalue = DC->getCVarValue( "m_pitch" ) < 0; + //s_controls.smoothmouse.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "m_filter" ) ); + //s_controls.alwaysrun.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_run" ) ); + //s_controls.autoswitch.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cg_autoswitch" ) ); + //s_controls.sensitivity.curvalue = UI_ClampCvar( 2, 30, Controls_GetCvarValue( "sensitivity" ) ); + //s_controls.joyenable.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "in_joystick" ) ); + //s_controls.joythreshold.curvalue = UI_ClampCvar( 0.05, 0.75, Controls_GetCvarValue( "joy_threshold" ) ); + //s_controls.freelook.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_freelook" ) ); +} + +/* +================= +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. +} + + +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; +} + +char g_nameBind1[32]; +char g_nameBind2[32]; + +void BindingFromName(const char *cvar) { + int i, b1, b2; + char sOR[32]; + + + // 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, 32 ); +// 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, 32 ); +// do NOT do this or it corrupts asian text!!! Q_strupr(g_nameBind2); + + trap_SP_GetStringTextString("MENUS_KEYBIND_OR",sOR, sizeof(sOR)); + + strcat( g_nameBind1, va(" %s ",sOR)); + strcat( g_nameBind1, g_nameBind2 ); + } + return; + } + } + strcpy(g_nameBind1, "???"); +} + +void Item_Slider_Paint(itemDef_t *item) { + vec4_t newColor, lowLight; + float x, y, value; + menuDef_t *parent = (menuDef_t*)item->parent; + + value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + + 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)); + } + + y = item->window.rect.y; + if (item->text) { + Item_Text_Paint(item); + x = item->textRect.x + item->textRect.w + 8; + } else { + x = item->window.rect.x; + } + DC->setColor(newColor); + DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT, DC->Assets.sliderBar ); + + x = Item_Slider_ThumbPosition(item); + DC->drawHandlePic( x - (SLIDER_THUMB_WIDTH / 2), y - 2, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT, DC->Assets.sliderThumb ); + +} + +void Item_Bind_Paint(itemDef_t *item) +{ + vec4_t newColor, lowLight; + float value; + int maxChars = 0; + float textScale,textWidth; + int 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 + { + memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); + } + + 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, item->iMenuFont); + startingXPos = (item->textRect.x + item->textRect.w + 8); + + while ((startingXPos + textWidth) >= SCREEN_WIDTH) + { + textScale -= .05f; + textWidth = DC->textWidth(g_nameBind1,(float) textScale, item->iMenuFont); + } + + // 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, item->iMenuFont); + yAdj = textHeight - DC->textHeight(g_nameBind1, textScale, item->iMenuFont); + } + + DC->drawText(startingXPos, item->textRect.y + yAdj, textScale, newColor, g_nameBind1, 0, maxChars, item->textStyle,item->iMenuFont); + } + else + { + DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle,item->iMenuFont); + } +} + +qboolean Display_KeyBindPending() { + return g_waitingForKey; +} + +qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) { + int id; + int i; + + if (key == A_MOUSE1 && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && !g_waitingForKey) + { + if (down) { + g_waitingForKey = qtrue; + g_bindItem = item; + } + return qtrue; + } + else if (key == A_ENTER && !g_waitingForKey) + { + if (down) + { + g_waitingForKey = qtrue; + g_bindItem = item; + } + 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; + return qtrue; + + case A_BACKSPACE: + id = BindingIDFromName(item->cvar); + if (id != -1) + { + if ( g_bindings[id].bind1 != -1 ) + { + DC->setBinding ( g_bindings[id].bind1, "" ); + } + + if ( g_bindings[id].bind2 != -1 ) + { + 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; + return qtrue; + + case '`': + return qtrue; + } + } + + if (key != -1) + { + + for (i=0; i < g_bindCount; i++) + { + + if (g_bindings[i].bind2 == key) { + g_bindings[i].bind2 = -1; + } + + 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; + + return qtrue; +} + +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; + } +} + +#ifndef CGAME +#include "../namespace_end.h" // Yes, these are inverted. The whole file is in the namespace. +extern void UI_SaberAttachToChar( itemDef_t *item ); +#include "../namespace_begin.h" +#endif + +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; + modelDef_t *modelPtr = (modelDef_t*)item->typeData; + + if (modelPtr == NULL) + { + return; + } + + // a moves datapad anim is playing +#ifndef CGAME + if (uiInfo.moveAnimTime && (uiInfo.moveAnimTime < uiInfo.uiDC.realTime)) + { + modelDef_t *modelPtr; + modelPtr = (modelDef_t*)item->typeData; + if (modelPtr) + { + char modelPath[MAX_QPATH]; + + Com_sprintf( modelPath, sizeof( modelPath ), "models/players/%s/model.glm", UI_Cvar_VariableString ( "ui_char_model" ) ); + //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 ); + ItemParse_asset_model_go( item, modelPath, &uiInfo.moveAnimTime ); + if ( !uiInfo.moveAnimTime ) + { + uiInfo.moveAnimTime = 500; + } + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_FORCEINAIR1: + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCELAND1].name ); + ItemParse_asset_model_go( item, modelPath, &uiInfo.moveAnimTime ); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_FORCEWALLRUNFLIP_START: + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCEWALLRUNFLIP_END].name ); + ItemParse_asset_model_go( item, modelPath, &uiInfo.moveAnimTime ); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_FORCELONGLEAP_START: + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCELONGLEAP_LAND].name ); + ItemParse_asset_model_go( item, modelPath, &uiInfo.moveAnimTime ); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_KNOCKDOWN3://on front - into force getup + trap_S_StartLocalSound( uiInfo.uiDC.Assets.moveJumpSound, CHAN_LOCAL ); + ItemParse_model_g2anim_go( item, animTable[BOTH_FORCE_GETUP_F1].name ); + ItemParse_asset_model_go( item, modelPath, &uiInfo.moveAnimTime ); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_KNOCKDOWN2://on back - kick forward getup + trap_S_StartLocalSound( uiInfo.uiDC.Assets.moveJumpSound, CHAN_LOCAL ); + ItemParse_model_g2anim_go( item, animTable[BOTH_GETUP_BROLL_F].name ); + ItemParse_asset_model_go( item, modelPath, &uiInfo.moveAnimTime ); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + case BOTH_KNOCKDOWN1://on back - roll-away + trap_S_StartLocalSound( uiInfo.uiDC.Assets.moveRollSound, CHAN_LOCAL ); + ItemParse_model_g2anim_go( item, animTable[BOTH_GETUP_BROLL_R].name ); + ItemParse_asset_model_go( item, modelPath, &uiInfo.moveAnimTime ); + uiInfo.moveAnimTime += uiInfo.uiDC.realTime; + break; + default: + ItemParse_model_g2anim_go( item, uiInfo.movesBaseAnim ); + ItemParse_asset_model_go( item, modelPath, &uiInfo.moveAnimTime ); + uiInfo.moveAnimTime = 0; + break; + } + + UI_UpdateCharacterSkin(); + + //update saber models + UI_SaberAttachToChar( item ); + } + } +#endif + + // 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->ghoul2) + { //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] ); + + // calculate distance so the model nearly fills the box + if (qtrue) + { + float len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + //origin[0] = len / tan(w/2); + } + else + { + origin[0] = item->textscale; + } + 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 = (int)((float)refdef.width / 640.0f * 90.0f); + //refdef.fov_y = atan2( refdef.height, refdef.width / tan( refdef.fov_x / 360 * M_PI ) ) * ( 360 / M_PI ); + + DC->clearScene(); + + refdef.time = DC->realTime; + + // add the model + + memset( &ent, 0, sizeof(ent) ); + + // use item storage to track + if (modelPtr->rotationSpeed) + { + VectorSet( angles, 0, modelPtr->angle + (float)refdef.time/modelPtr->rotationSpeed, 0 ); + } + else + { + VectorSet( angles, 0, modelPtr->angle, 0 ); + } + + if ( (item->flags&ITF_ISANYSABER) && !(item->flags&ITF_ISCHARACTER) ) + {//hack to put saber on it's side + VectorSet( angles, modelPtr->angle, 0, 90 ); + } + + AnglesToAxis( angles, ent.axis ); + + if (item->ghoul2) + { + ent.ghoul2 = item->ghoul2; + ent.radius = 1000; + ent.customSkin = modelPtr->g2skin; + + VectorCopy(modelPtr->g2scale, ent.modelScale); + UI_ScaleModelAxis(&ent); +#ifndef CGAME + 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, angles ); + } +#endif + } + else + { + ent.hModel = item->asset; + } + VectorCopy( origin, ent.origin ); + VectorCopy( ent.origin, ent.oldorigin ); + + // Set up lighting + VectorCopy( origin, ent.lightingOrigin ); + ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; + + DC->addRefEntityToScene( &ent ); + DC->renderScene( &refdef ); + +} + + +void Item_Image_Paint(itemDef_t *item) { + if (item == NULL) { + return; + } + DC->drawHandlePic(item->window.rect.x+1, item->window.rect.y+1, item->window.rect.w-2, item->window.rect.h-2, item->asset); +} + + +void Item_TextScroll_Paint(itemDef_t *item) +{ + char cvartext[1024]; + float x, y, size, count, thumb; + int i; + textScrollDef_t *scrollPtr = (textScrollDef_t*)item->typeData; + + count = scrollPtr->iLineCount; + + // 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; + + scrollPtr->endPos = scrollPtr->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_TextScroll_ThumbDrawPosition(item); + if (thumb > y - SCROLLBAR_SIZE - 1) + { + thumb = y - SCROLLBAR_SIZE - 1; + } + DC->drawHandlePic(x, thumb, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb); + + if (item->cvar) + { + DC->getCVarString(item->cvar, cvartext, sizeof(cvartext)); + item->text = cvartext; + Item_TextScroll_BuildLines ( item ); + } + + // 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, 0, item->textStyle, item->iMenuFont); + + size -= scrollPtr->lineHeight; + if (size < scrollPtr->lineHeight) + { + scrollPtr->drawPadding = scrollPtr->lineHeight - size; + break; + } + + scrollPtr->endPos++; + y += scrollPtr->lineHeight; + } +} + +#define COLOR_MAX 255.0f + +// Draw routine for list boxes +void Item_ListBox_Paint(itemDef_t *item) { + float x, y, sizeWidth, count, i, i2,sizeHeight, thumb; + int startPos; + qhandle_t image; + qhandle_t optionalImage1, optionalImage2, optionalImage3; + listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; +//JLF MPMOVED + int numlines; + + + // 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); + +//JLFLISTBOX MPMOVED +#ifdef _XBOX + listPtr->startPos = listPtr->cursorPos; + //item->cursorPos = listPtr->startPos; +#endif +//JLFLISTBOX MPMOVED + if (listPtr->startPos > (count?count-1:count)) + {//probably changed feeders, so reset + listPtr->startPos = 0; + } +//JLF END + if (item->cursorPos > (count?count-1:count)) + {//probably changed feeders, so reset + item->cursorPos = (count?count-1:count); + // NOTE : might consider moving this to any spot in here we change the cursor position + DC->feederSelection( item->special, item->cursorPos, NULL ); + } + + + + // default is vertical if horizontal flag is not here + if (item->window.flags & WINDOW_HORIZONTAL) + { +#ifdef _DEBUG + const char *text; +#endif + +//JLF new variable (code just indented) MPMOVED + 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; + sizeWidth = item->window.rect.w - (SCROLLBAR_SIZE * 2); + DC->drawHandlePic(x, y, sizeWidth+1, SCROLLBAR_SIZE, DC->Assets.scrollBar); + x += sizeWidth - 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; + sizeWidth = 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) + { +#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++; + // fit++; + } + } + else + { + // + } + +#ifdef _DEBUG + // Show pic name + text = DC->feederItemText(item->special, item->cursorPos, 0, &optionalImage1, &optionalImage2, &optionalImage3); + if (text) + { + DC->drawText(item->window.rect.x, item->window.rect.y+item->window.rect.h, item->textscale, item->window.foreColor, text, 0, 0, item->textStyle, item->iMenuFont); + } +#endif + + } + // A vertical list box + else + { +//JLF MPMOVED + numlines = item->window.rect.h / listPtr->elementHeight; +//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; + sizeHeight = item->window.rect.h - (SCROLLBAR_SIZE * 2); + DC->drawHandlePic(x, y, SCROLLBAR_SIZE, sizeHeight+1, DC->Assets.scrollBar); + y += sizeHeight - 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); + } +//JLF end + + // adjust size for item painting + 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; + } + // NOTE : Is endPos supposed to be valid or not? It was being used as a valid entry but I changed those + // few spots that were causing bugs + listPtr->endPos++; + startPos = listPtr->endPos; + y += listPtr->elementHeight; + + } + } + // single column + else + { + 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++; + sizeHeight -= listPtr->elementHeight; + if (sizeHeight < listPtr->elementHeight) + { + listPtr->drawPadding = listPtr->elementHeight - sizeHeight; + break; + } + y += listPtr->elementHeight; + // fit++; + } + } + } + else + { + x = item->window.rect.x + 1; + y = item->window.rect.y + 1; +//JLF MPMOVED + y = item->window.rect.y + 1 - listPtr->elementHeight; +#ifdef _XBOX + i = listPtr->startPos - (numlines/2); +#else + i = listPtr->startPos; +#endif + + for (; i < count; i++) +//JLF END + { + const char *text; + // always draw at least one + // which may overdraw the box if it is too small for the element + if (listPtr->numColumns > 0) { + int j;//, subX = listPtr->elementHeight; + + for (j = 0; j < listPtr->numColumns; j++) + { + char temp[MAX_STRING_CHARS]; + int imageStartX = listPtr->columnInfo[j].pos; + text = DC->feederItemText(item->special, i, j, &optionalImage1, &optionalImage2, &optionalImage3); + + if( !text ) + { + continue; + } + + if (text[0]=='@') + { + trap_SP_GetStringTextString( &text[1] , temp, sizeof(temp)); + text = temp; + } + + /* + 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 ) + { +//JLF MPMOVED +#ifdef _XBOX + float fScaleA = item->textscale; +#endif + int textyOffset; +#ifdef _XBOX + textyOffset = DC->textHeight (text, fScaleA, item->iMenuFont); + textyOffset *= -1; + textyOffset /=2; + textyOffset += listPtr->elementHeight/2; +#else + textyOffset = 0; +#endif +// DC->drawText(x + 4 + listPtr->columnInfo[j].pos, y + listPtr->elementHeight, item->textscale, item->window.foreColor, text, 0, listPtr->columnInfo[j].maxChars, item->textStyle); + //WAS LAST DC->drawText(x + 4 + listPtr->columnInfo[j].pos, y, item->textscale, item->window.foreColor, text, 0, listPtr->columnInfo[j].maxChars, item->textStyle, item->iMenuFont); + DC->drawText(x + 4 + listPtr->columnInfo[j].pos, y + listPtr->elementHeight+ textyOffset + item->textaligny, item->textscale, item->window.foreColor, text, 0,listPtr->columnInfo[j].maxChars, item->textStyle, item->iMenuFont); + + +//JLF END + } + if ( j < listPtr->numColumns - 1 ) + { + imageStartX = listPtr->columnInfo[j+1].pos; + } + DC->setColor( NULL ); + if (optionalImage3 >= 0) + { + DC->drawHandlePic(imageStartX - listPtr->elementHeight*3, y+listPtr->elementHeight+2, listPtr->elementHeight, listPtr->elementHeight, optionalImage3); + } + if (optionalImage2 >= 0) + { + DC->drawHandlePic(imageStartX - listPtr->elementHeight*2, y+listPtr->elementHeight+2, listPtr->elementHeight, listPtr->elementHeight, optionalImage2); + } + if (optionalImage1 >= 0) + { + DC->drawHandlePic(imageStartX - listPtr->elementHeight, y+listPtr->elementHeight+2, listPtr->elementHeight, listPtr->elementHeight, optionalImage1); + } + } + } + else + { +#ifdef _XBOX + if (i >= 0) + { +#endif + text = DC->feederItemText(item->special, i, 0, &optionalImage1, &optionalImage2, &optionalImage3 ); + if ( optionalImage1 >= 0 || optionalImage2 >= 0 || optionalImage3 >= 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, item->textscale, item->window.foreColor, text, 0, 0, item->textStyle); + DC->drawText(x + 4, y + item->textaligny, item->textscale, item->window.foreColor, text, 0, 0, item->textStyle, item->iMenuFont); + } +#ifdef _XBOX + } +#endif + } + + if (i == item->cursorPos) + { + DC->fillRect(x + 2, y + listPtr->elementHeight + 2, item->window.rect.w - SCROLLBAR_SIZE - 4, listPtr->elementHeight, item->window.outlineColor); + } + + sizeHeight -= listPtr->elementHeight; + if (sizeHeight < listPtr->elementHeight) + { + listPtr->drawPadding = listPtr->elementHeight - sizeHeight; + break; + } + listPtr->endPos++; + y += listPtr->elementHeight; + // fit++; + } + } + } +} + + +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) { + 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->disabled) + { + memcpy(color, parent->disableColor, sizeof(vec4_t)); // bk001207 - FIXME: Com_Memcpy + } + + if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { + memcpy(color, parent->disableColor, sizeof(vec4_t)); // bk001207 - FIXME: Com_Memcpy + } + + if (item->text) { + Item_Text_Paint(item); + if (item->text[0]) { + // +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->iMenuFont); + } else { + DC->ownerDrawItem(item->textRect.x + item->textRect.w, 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->iMenuFont); + } + } 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->iMenuFont); + } + } +} + + +void Item_Paint(itemDef_t *item) +{ + vec4_t red; + menuDef_t *parent = (menuDef_t*)item->parent; + int xPos,textWidth; + vec4_t color = {1, 1, 1, 1}; + + red[0] = red[3] = 1; + red[1] = red[2] = 0; + + if (item == NULL) + { + return; + } + + 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 = 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; + } + } + + if (item->window.flags & WINDOW_TIMEDVISIBLE) { + + } + + if (!(item->window.flags & WINDOW_VISIBLE)) + { + return; + } + + +//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 + { // Draw the desctext + const char *textPtr = item->descText; + if (*textPtr == '@') // string reference + { + char temp[MAX_STRING_CHARS]; + trap_SP_GetStringTextString( &textPtr[1] , temp, sizeof(temp)); + textPtr = temp; + } + + Item_TextColor(item, &color); + + {// stupid C language + float fDescScale = parent->descScale ? parent->descScale : 1; + float fDescScaleCopy = fDescScale; + int iYadj = 0; + while (1) + { + textWidth = DC->textWidth(textPtr,fDescScale, FONT_SMALL2); + + 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... + // + if (fDescScale != fDescScaleCopy) + { + int iOriginalTextHeight = DC->textHeight(textPtr, fDescScaleCopy, FONT_MEDIUM); + iYadj = iOriginalTextHeight - DC->textHeight(textPtr, fDescScale, FONT_MEDIUM); + } + + DC->drawText(xPos, parent->descY + iYadj, fDescScale, parent->descColor, textPtr, 0, 0, item->textStyle, FONT_SMALL2); + break; + } + } + } + } + } + + // paint the rect first.. + Window_Paint(&item->window, parent->fadeAmount , parent->fadeClamp, parent->fadeCycle); + + // Draw box to show rectangle extents, in debug mode + if (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_IMAGE: + // Item_Image_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; + } + +} + +void Menu_Init(menuDef_t *menu) { + memset(menu, 0, sizeof(menuDef_t)); + menu->cursorItem = -1; + menu->fadeAmount = DC->Assets.fadeAmount; + menu->fadeClamp = DC->Assets.fadeClamp; + menu->fadeCycle = DC->Assets.fadeCycle; + Window_Init(&menu->window); +} + +itemDef_t *Menu_GetFocusedItem(menuDef_t *menu) { + int i; + if (menu) { + for (i = 0; i < menu->itemCount; i++) { + if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { + return menu->items[i]; + } + } + } + return NULL; +} + +menuDef_t *Menu_GetFocused() { + 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; +} + +void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down) { + if (menu) { + int i; + for (i = 0; i < menu->itemCount; i++) { + if (menu->items[i]->special == feeder) { + Item_ListBox_HandleKey(menu->items[i], (down) ? A_CURSOR_DOWN : A_CURSOR_UP, qtrue, qtrue); + return; + } + } + } +} + + + +void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name) { + if (menu == NULL) { + if (name == NULL) { + menu = Menu_GetFocused(); + } else { + menu = Menus_FindByName(name); + } + } + + if (menu) { + int i; + for (i = 0; i < menu->itemCount; i++) { + if (menu->items[i]->special == feeder) { + if (index == 0) { + listBoxDef_t *listPtr = (listBoxDef_t*)menu->items[i]->typeData; + listPtr->cursorPos = 0; + listPtr->startPos = 0; + } + menu->items[i]->cursorPos = index; + DC->feederSelection(menu->items[i]->special, menu->items[i]->cursorPos, NULL); + return; + } + } + } +} + +qboolean Menus_AnyFullScreenVisible() { + int i; + for (i = 0; i < menuCount; i++) { + if (Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen) { + return qtrue; + } + } + return qfalse; +} + +menuDef_t *Menus_ActivateByName(const char *p) { + int i; + menuDef_t *m = NULL; + menuDef_t *focus = Menu_GetFocused(); + for (i = 0; i < menuCount; i++) { + 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; + } + } + Display_CloseCinematics(); + + // 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; +} + + +void Item_Init(itemDef_t *item) { + memset(item, 0, sizeof(itemDef_t)); + item->textscale = 0.55f; + Window_Init(&item->window); +} + +void Menu_HandleMouseMove(menuDef_t *menu, float x, float y) { + + //JLFMOUSE I THINK THIS JUST SETS THE FOCUS BASED ON THE MOUSE +#ifdef _XBOX + return ; +#endif + //END JLF + + + int i, pass; + qboolean focusSet = qfalse; + + itemDef_t *overItem; + if (menu == NULL) { + return; + } + + if (!(menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) { + return; + } + + if (itemCapture) { + //Item_MouseMove(itemCapture, x, y); + return; + } + + if (g_waitingForKey || g_editingField) { + return; + } + + // FIXME: this is the whole issue of focus vs. mouse over.. + // need a better overall solution as i don't like going through everything twice + for (pass = 0; pass < 2; pass++) { + 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]->disabled) + { + continue; + } + + // items can be enabled and disabled based on cvars + 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 (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) { + if (pass == 1) { + overItem = menu->items[i]; + if (overItem->type == ITEM_TYPE_TEXT && overItem->text) { + if (!Rect_ContainsPoint(&overItem->window.rect, x, y)) { + continue; + } + } + // if we are over an item + if (IsVisible(overItem->window.flags)) { + // different one + Item_MouseEnter(overItem, x, y); + // Item_SetMouseOver(overItem, qtrue); + + // if item is not a decoration see if it can take focus + if (!focusSet) { + focusSet = Item_SetFocus(overItem, x, y); + } + } + } + } else if (menu->items[i]->window.flags & WINDOW_MOUSEOVER) { + Item_MouseLeave(menu->items[i]); + Item_SetMouseOver(menu->items[i], qfalse); + } + } + } + +} + +void Menu_Paint(menuDef_t *menu, qboolean forcePaint) { + int i; + + if (menu == NULL) { + return; + } + + 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) { + // implies a background shader + // FIXME: make sure we have a default shader if fullscreen is set with no background + 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 + for (i = 0; i < menu->itemCount; i++) + { + if (!menu->items[i]->appearanceSlot) + { + Item_Paint(menu->items[i]); + } + else // Timed order of appearance + { + if (menu->appearanceTime < DC->realTime) // Time to show another item + { + menu->appearanceTime = DC->realTime + menu->appearanceIncrement; + menu->appearanceCnt++; + } + + if (menu->items[i]->appearanceSlot<=menu->appearanceCnt) + { + Item_Paint(menu->items[i]); + } + } + } + + if (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_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)); + } +} + +/* +=============== +Keyword Hash +=============== +*/ + +#define KEYWORDHASH_SIZE 512 + +typedef struct keywordHash_s +{ + char *keyword; + qboolean (*func)(itemDef_t *item, int handle); + struct keywordHash_s *next; +} keywordHash_t; + +int KeywordHash_Key(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; +} + +void KeywordHash_Add(keywordHash_t *table[], keywordHash_t *key) { + int hash; + + hash = KeywordHash_Key(key->keyword); +/* + if (table[hash]) { + int collision = qtrue; + } +*/ + key->next = table[hash]; + table[hash] = key; +} + +keywordHash_t *KeywordHash_Find(keywordHash_t *table[], 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; +} + +/* +=============== +Item Keyword Parse functions +=============== +*/ + +// name +qboolean ItemParse_name( itemDef_t *item, int handle ) { + if (!PC_String_Parse(handle, &item->window.name)) { + return qfalse; + } + return qtrue; +} + +// name +qboolean ItemParse_focusSound( itemDef_t *item, int handle ) { + pc_token_t token; + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + item->focusSound = DC->registerSound(token.string); + return qtrue; +} + + +// text +qboolean ItemParse_text( itemDef_t *item, int handle ) { + if (!PC_String_Parse(handle, &item->text)) { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_descText + text +=============== +*/ +qboolean ItemParse_descText( itemDef_t *item, int handle) +{ + + if (!PC_String_Parse(handle, &item->descText)) + { + return qfalse; + } + + return qtrue; + +} + + +/* +=============== +ItemParse_text + text +=============== +*/ +qboolean ItemParse_text2( itemDef_t *item, int handle) +{ + + if (!PC_String_Parse(handle, &item->text2)) + { + return qfalse; + } + + return qtrue; + +} + +/* +=============== +ItemParse_text2alignx +=============== +*/ +qboolean ItemParse_text2alignx( itemDef_t *item, int handle) +{ + if (!PC_Float_Parse(handle, &item->text2alignx)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_text2aligny +=============== +*/ +qboolean ItemParse_text2aligny( itemDef_t *item, int handle) +{ + if (!PC_Float_Parse(handle, &item->text2aligny)) + { + return qfalse; + } + return qtrue; +} + +// group +qboolean ItemParse_group( itemDef_t *item, int handle ) { + if (!PC_String_Parse(handle, &item->window.group)) { + return qfalse; + } + return qtrue; +} + +typedef struct uiG2PtrTracker_s uiG2PtrTracker_t; + +struct uiG2PtrTracker_s +{ + void *ghoul2; + uiG2PtrTracker_t *next; +}; + +uiG2PtrTracker_t *ui_G2PtrTracker = NULL; + +//rww - UI G2 shared management functions. + +//Insert the pointer into our chain so we can keep track of it for freeing. +void UI_InsertG2Pointer(void *ghoul2) +{ + uiG2PtrTracker_t **nextFree = &ui_G2PtrTracker; + + while ((*nextFree) && (*nextFree)->ghoul2) + { //check if it has a ghoul2, if not we can reuse it. + nextFree = &((*nextFree)->next); + } + + if (!nextFree) + { //shouldn't happen + assert(0); + return; + } + + if (!(*nextFree)) + { //if we aren't reusing a chain then allocate space for it. + (*nextFree) = (uiG2PtrTracker_t *)BG_Alloc(sizeof(uiG2PtrTracker_t)); + (*nextFree)->next = NULL; + } + + (*nextFree)->ghoul2 = ghoul2; +} + +//Remove a ghoul2 pointer from the chain if it's there. +void UI_ClearG2Pointer(void *ghoul2) +{ + uiG2PtrTracker_t *next = ui_G2PtrTracker; + + if (!ghoul2) + { + return; + } + + while (next) + { + if (next->ghoul2 == ghoul2) + { //found it, set it to null so we can reuse this link. + next->ghoul2 = NULL; + break; + } + + next = next->next; + } +} + +//Called on shutdown, cleans up all the ghoul2 instances laying around. +void UI_CleanupGhoul2(void) +{ + uiG2PtrTracker_t *next = ui_G2PtrTracker; + + while (next) + { + if (next->ghoul2 && trap_G2_HaveWeGhoul2Models(next->ghoul2)) + { //found a g2 instance, clean it. + trap_G2API_CleanGhoul2Models(&next->ghoul2); + } + + next = next->next; + } + +#ifdef _XBOX + ui_G2PtrTracker = NULL; +#endif +} + +// asset_model +int UI_ParseAnimationFile(const char *filename, animation_t *animset, qboolean isHumanoid); + +/* +=============== +ItemParse_asset_model + asset_model +=============== +*/ +qboolean ItemParse_asset_model_go( itemDef_t *item, const char *name,int *runTimeLength ) +{ +#ifndef CGAME + int g2Model; + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + *runTimeLength =0.0f; + + if (!Q_stricmp(&name[strlen(name) - 4], ".glm")) + { //it's a ghoul2 model then + if ( item->ghoul2 ) + { + UI_ClearG2Pointer(item->ghoul2); //remove from tracking list + trap_G2API_CleanGhoul2Models(&item->ghoul2); //remove ghoul info + item->flags &= ~ITF_G2VALID; + } + + g2Model = trap_G2API_InitGhoul2Model(&item->ghoul2, name, 0, modelPtr->g2skin, 0, 0, 0); + if (g2Model >= 0) + { + UI_InsertG2Pointer(item->ghoul2); //remember it so we can free it when the ui shuts down. + 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); + + char GLAName[MAX_QPATH]; + + GLAName[0] = 0; + trap_G2API_GetGLAName(item->ghoul2, 0, GLAName); + + if (GLAName[0]) + { + int animIndex; + char *slash; + + slash = Q_strrchr( GLAName, '/' ); + + if ( slash ) + { //If this isn't true the gla path must be messed up somehow. + strcpy(slash, "/animation.cfg"); + + animIndex = UI_ParseAnimationFile(GLAName, NULL, qfalse); + if (animIndex != -1) + { //We parsed out the animation info for whatever model this is + animation_t *anim = &bgAllAnims[animIndex].anims[modelPtr->g2anim]; + + int sFrame = anim->firstFrame; + int eFrame = anim->firstFrame + anim->numFrames; + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int time = DC->realTime; + float animSpeed = 50.0f / anim->frameLerp; + int blendTime = 150; + + if (anim->loopFrames != -1) + { + flags |= BONE_ANIM_OVERRIDE_LOOP; + } + + trap_G2API_SetBoneAnim(item->ghoul2, 0, "model_root", sFrame, eFrame, flags, animSpeed, time, -1, blendTime); + *runTimeLength =((anim->frameLerp * (anim->numFrames-2))); + } + } + } + } + + 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 + //trap_G2API_InitGhoul2Model(&item->ghoul2, name, 0, modelPtr->g2skin, 0, 0, 0); + //ahh, what are you doing?! + trap_G2API_SetSkin(item->ghoul2, 0, modelPtr->g2skin, modelPtr->g2skin); + } + } + /* + else + { + Com_Error(ERR_FATAL, "%s does not exist.", name); + } + */ + } + else if(!(item->asset)) + { //guess it's just an md3 + item->asset = DC->registerModel(name); + item->flags &= ~ITF_G2VALID; + } +#endif + return qtrue; +} + +qboolean ItemParse_asset_model( itemDef_t *item, int handle ) { + const char *temp; + modelDef_t *modelPtr; + int animRunLength; + pc_token_t token; + + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + temp = token.string; + +#ifndef CGAME + if (!stricmp(token.string,"ui_char_model") ) + { + char modelPath[MAX_QPATH]; + char ui_char_model[MAX_QPATH]; + trap_Cvar_VariableStringBuffer("ui_char_model", ui_char_model, sizeof(ui_char_model) ); + Com_sprintf( modelPath, sizeof( modelPath ), "models/players/%s/model.glm", ui_char_model ); + temp = modelPath; + } +#endif + return (ItemParse_asset_model_go( item, temp, &animRunLength )); +} + +// asset_shader +qboolean ItemParse_asset_shader( itemDef_t *item, int handle ) { + pc_token_t token; + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + item->asset = DC->registerShaderNoMip(token.string); + return qtrue; +} + +// model_origin +qboolean ItemParse_model_origin( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_Float_Parse(handle, &modelPtr->origin[0])) { + if (PC_Float_Parse(handle, &modelPtr->origin[1])) { + if (PC_Float_Parse(handle, &modelPtr->origin[2])) { + return qtrue; + } + } + } + return qfalse; +} + +// model_fovx +qboolean ItemParse_model_fovx( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!PC_Float_Parse(handle, &modelPtr->fov_x)) { + return qfalse; + } + return qtrue; +} + +// model_fovy +qboolean ItemParse_model_fovy( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!PC_Float_Parse(handle, &modelPtr->fov_y)) { + return qfalse; + } + return qtrue; +} + +// model_rotation +qboolean ItemParse_model_rotation( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!PC_Int_Parse(handle, &modelPtr->rotationSpeed)) { + return qfalse; + } + return qtrue; +} + +// model_angle +qboolean ItemParse_model_angle( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!PC_Int_Parse(handle, &modelPtr->angle)) { + return qfalse; + } + return qtrue; +} + +// model_g2mins +qboolean ItemParse_model_g2mins( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_Float_Parse(handle, &modelPtr->g2mins[0])) { + if (PC_Float_Parse(handle, &modelPtr->g2mins[1])) { + if (PC_Float_Parse(handle, &modelPtr->g2mins[2])) { + return qtrue; + } + } + } + return qfalse; +} + +// model_g2maxs +qboolean ItemParse_model_g2maxs( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_Float_Parse(handle, &modelPtr->g2maxs[0])) { + if (PC_Float_Parse(handle, &modelPtr->g2maxs[1])) { + if (PC_Float_Parse(handle, &modelPtr->g2maxs[2])) { + return qtrue; + } + } + } + return qfalse; +} + +// model_g2scale +qboolean ItemParse_model_g2scale( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (PC_Float_Parse(handle, &modelPtr->g2scale[0])) { + if (PC_Float_Parse(handle, &modelPtr->g2scale[1])) { + if (PC_Float_Parse(handle, &modelPtr->g2scale[2])) { + return qtrue; + } + } + } + return qfalse; +} + +// model_g2skin +qhandle_t trap_R_RegisterSkin( const char *name ); + +qboolean ItemParse_model_g2skin( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + pc_token_t token; + + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + + if (!token.string[0]) + { //it was parsed correctly so still return true. + return qtrue; + } + + modelPtr->g2skin = trap_R_RegisterSkin(token.string); + + return qtrue; +} + +// model_g2anim +qboolean ItemParse_model_g2anim( itemDef_t *item, int handle ) { + modelDef_t *modelPtr; + pc_token_t token; + int i = 0; + + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + + if ( !token.string[0]) + { //it was parsed correctly so still return true. + return qtrue; + } + + while (i < MAX_ANIMATIONS) + { + if (!Q_stricmp(token.string, animTable[i].name)) + { //found it + modelPtr->g2anim = i; + return qtrue; + } + i++; + } + + Com_Printf("Could not find '%s' in the anim table\n", token.string); + return qtrue; +} + +// model_g2skin +qboolean ItemParse_model_g2skin_go( itemDef_t *item, const char *skinName ) +{ + + modelDef_t *modelPtr; + int defSkin; + + + Item_ValidateTypeData(item); + modelPtr = (modelDef_t*)item->typeData; + + if (!skinName || !skinName[0]) + { //it was parsed correctly so still return true. + modelPtr->g2skin = 0; + trap_G2API_SetSkin(item->ghoul2, 0, 0, 0); + + return qtrue; + } + + // set skin + if ( item->ghoul2 ) + { + defSkin = trap_R_RegisterSkin(skinName); + trap_G2API_SetSkin(item->ghoul2, 0, defSkin, defSkin); + } + + return qtrue; +} + +// 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; +} + +// Get the cvar, get the values and stuff them in the rect structure. +qboolean ItemParse_rectcvar( itemDef_t *item, int handle ) +{ + char cvarBuf[1024]; + const char *holdVal; + char *holdBuf; + + // get Cvar name + pc_token_t token; + if (!trap_PC_ReadToken(handle, &token)) + { + return qfalse; + } + + // get cvar data + DC->getCVarString(token.string, cvarBuf, sizeof(cvarBuf)); + + holdBuf = cvarBuf; + if (String_Parse(&holdBuf,&holdVal)) + { + item->window.rectClient.x = atof(holdVal); + if (String_Parse(&holdBuf,&holdVal)) + { + item->window.rectClient.y = atof(holdVal); + if (String_Parse(&holdBuf,&holdVal)) + { + item->window.rectClient.w = atof(holdVal); + if (String_Parse(&holdBuf,&holdVal)) + { + item->window.rectClient.h = atof(holdVal); + return qtrue; + } + } + } + } + + // There may be no cvar built for this, and that's okay. . . I guess. + return qtrue; +} + +// rect +qboolean ItemParse_rect( itemDef_t *item, int handle ) { + if (!PC_Rect_Parse(handle, &item->window.rectClient)) { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_flag + style +=============== +*/ +qboolean ItemParse_flag( itemDef_t *item, int handle) +{ + int i; + pc_token_t token; + + if (!trap_PC_ReadToken(handle, &token)) + { + return qfalse; + } + + i=0; + while (styles[i]) + { + if (Q_stricmp(token.string,itemFlags[i].string)==0) + { + item->window.flags |= itemFlags[i].value; + break; + } + i++; + } + + if (itemFlags[i].string == NULL) + { + Com_Printf(va( S_COLOR_YELLOW "Unknown item style value '%s'",token.string)); + } + + return qtrue; +} + +/* +=============== +ItemParse_style + style +=============== +*/ +qboolean ItemParse_style( itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->window.style)) + { + Com_Printf(S_COLOR_YELLOW "Unknown item style value"); + return qfalse; + } + + return qtrue; +} + + +// decoration +qboolean ItemParse_decoration( itemDef_t *item, int handle ) { + item->window.flags |= WINDOW_DECORATION; + return qtrue; +} + +// notselectable +qboolean ItemParse_notselectable( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + Item_ValidateTypeData(item); + listPtr = (listBoxDef_t*)item->typeData; + if (item->type == ITEM_TYPE_LISTBOX && listPtr) { + listPtr->notselectable = qtrue; + } + return qtrue; +} + +/* +=============== +ItemParse_scrollhidden + scrollhidden +=============== +*/ +qboolean ItemParse_scrollhidden( itemDef_t *item , int handle) +{ + listBoxDef_t *listPtr; + Item_ValidateTypeData(item); + listPtr = (listBoxDef_t*)item->typeData; + + if (item->type == ITEM_TYPE_LISTBOX && listPtr) + { + listPtr->scrollhidden = qtrue; + } + return qtrue; +} + + + + + +// manually wrapped +qboolean ItemParse_wrapped( itemDef_t *item, int handle ) { + item->window.flags |= WINDOW_WRAPPED; + return qtrue; +} + +// auto wrapped +qboolean ItemParse_autowrapped( itemDef_t *item, int handle ) { + item->window.flags |= WINDOW_AUTOWRAPPED; + return qtrue; +} + + +// horizontalscroll +qboolean ItemParse_horizontalscroll( itemDef_t *item, int handle ) { + item->window.flags |= WINDOW_HORIZONTAL; + return qtrue; +} + +/* +=============== +ItemParse_type + type +=============== +*/ +qboolean ItemParse_type( itemDef_t *item, int handle ) +{ +// int i,holdInt; + + if (!PC_Int_Parse(handle, &item->type)) + { + return qfalse; + } + Item_ValidateTypeData(item); + return qtrue; +} + +// elementwidth, used for listbox image elements +// uses textalignx for storage +qboolean ItemParse_elementwidth( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + listPtr = (listBoxDef_t*)item->typeData; + if (!PC_Float_Parse(handle, &listPtr->elementWidth)) { + return qfalse; + } + return qtrue; +} + +// elementheight, used for listbox image elements +// uses textaligny for storage +qboolean ItemParse_elementheight( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + listPtr = (listBoxDef_t*)item->typeData; + if (!PC_Float_Parse(handle, &listPtr->elementHeight)) { + return qfalse; + } + return qtrue; +} + +// feeder +qboolean ItemParse_feeder( itemDef_t *item, int handle ) { + if (!PC_Float_Parse(handle, &item->special)) { + return qfalse; + } + return qtrue; +} + +// elementtype, used to specify what type of elements a listbox contains +// uses textstyle for storage +qboolean ItemParse_elementtype( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + return qfalse; + listPtr = (listBoxDef_t*)item->typeData; + if (!PC_Int_Parse(handle, &listPtr->elementStyle)) { + return qfalse; + } + return qtrue; +} + +// columns sets a number of columns and an x pos and width per.. +qboolean ItemParse_columns( itemDef_t *item, int handle ) { + int num, i; + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + return qfalse; + listPtr = (listBoxDef_t*)item->typeData; + if (PC_Int_Parse(handle, &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_Int_Parse(handle, &pos) && PC_Int_Parse(handle, &width) && PC_Int_Parse(handle, &maxChars)) { + listPtr->columnInfo[i].pos = pos; + listPtr->columnInfo[i].width = width; + listPtr->columnInfo[i].maxChars = maxChars; + } else { + return qfalse; + } + } + } else { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_border( itemDef_t *item, int handle ) { + if (!PC_Int_Parse(handle, &item->window.border)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_bordersize( itemDef_t *item, int handle ) { + if (!PC_Float_Parse(handle, &item->window.borderSize)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_visible( itemDef_t *item, int handle ) { + int i; + + if (!PC_Int_Parse(handle, &i)) { + return qfalse; + } + if (i) { + item->window.flags |= WINDOW_VISIBLE; + } + return qtrue; +} + +qboolean ItemParse_ownerdraw( itemDef_t *item, int handle ) { + if (!PC_Int_Parse(handle, &item->window.ownerDraw)) { + return qfalse; + } + item->type = ITEM_TYPE_OWNERDRAW; + return qtrue; +} + +qboolean ItemParse_align( itemDef_t *item, int handle ) { + if (!PC_Int_Parse(handle, &item->alignment)) { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_isCharacter + +Sets item flag showing this is a character +=============== +*/ +qboolean ItemParse_isCharacter( itemDef_t *item, int handle ) +{ + int flag; + + if (PC_Int_Parse(handle, &flag)) + { + if ( flag ) + { + item->flags |= ITF_ISCHARACTER; + } + else + { + item->flags &= ~ITF_ISCHARACTER; + } + return qtrue; + } + return qfalse; +} + + +/* +=============== +ItemParse_textalign +=============== +*/ +qboolean ItemParse_textalign( itemDef_t *item, int handle ) +{ + if (!PC_Int_Parse(handle, &item->textalignment)) + { + Com_Printf(S_COLOR_YELLOW "Unknown text alignment value"); + + return qfalse; + } + + return qtrue; +} + +qboolean ItemParse_textalignx( itemDef_t *item, int handle ) { + if (!PC_Float_Parse(handle, &item->textalignx)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_textaligny( itemDef_t *item, int handle ) { + if (!PC_Float_Parse(handle, &item->textaligny)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_textscale( itemDef_t *item, int handle ) { + if (!PC_Float_Parse(handle, &item->textscale)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_textstyle( itemDef_t *item, int handle ) { + if (!PC_Int_Parse(handle, &item->textStyle)) { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_invertyesno +=============== +*/ +//JLFYESNO MPMOVED +qboolean ItemParse_invertyesno( itemDef_t *item, int handle) +{ + if (!PC_Int_Parse(handle, &item->invertYesNo)) + { + return qfalse; + } + return qtrue; +} + +/* +=============== +ItemParse_xoffset (used for yes/no and multi) +=============== +*/ +qboolean ItemParse_xoffset( itemDef_t *item, int handle) +{ + if (PC_Int_Parse(handle, &item->xoffset)) + { + return qfalse; + } + return qtrue; +} + + +qboolean ItemParse_backcolor( itemDef_t *item, int handle ) { + int i; + float f; + + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &f)) { + return qfalse; + } + item->window.backColor[i] = f; + } + return qtrue; +} + +qboolean ItemParse_forecolor( itemDef_t *item, int handle ) { + int i; + float f; + + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &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; +} + +qboolean ItemParse_bordercolor( itemDef_t *item, int handle ) { + int i; + float f; + + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &f)) { + return qfalse; + } + item->window.borderColor[i] = f; + } + return qtrue; +} + +qboolean ItemParse_outlinecolor( itemDef_t *item, int handle ) { + if (!PC_Color_Parse(handle, &item->window.outlineColor)){ + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_background( itemDef_t *item, int handle ) { + pc_token_t token; + + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + item->window.background = DC->registerShaderNoMip(token.string); + return qtrue; +} + +qboolean ItemParse_cinematic( itemDef_t *item, int handle ) { + if (!PC_String_Parse(handle, &item->window.cinematicName)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_doubleClick( itemDef_t *item, int handle ) { + listBoxDef_t *listPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) { + return qfalse; + } + + listPtr = (listBoxDef_t*)item->typeData; + + if (!PC_Script_Parse(handle, &listPtr->doubleClick)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_onFocus( itemDef_t *item, int handle ) { + if (!PC_Script_Parse(handle, &item->onFocus)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_leaveFocus( itemDef_t *item, int handle ) { + if (!PC_Script_Parse(handle, &item->leaveFocus)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_mouseEnter( itemDef_t *item, int handle ) { + if (!PC_Script_Parse(handle, &item->mouseEnter)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_mouseExit( itemDef_t *item, int handle ) { + if (!PC_Script_Parse(handle, &item->mouseExit)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle ) { + if (!PC_Script_Parse(handle, &item->mouseEnterText)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_mouseExitText( itemDef_t *item, int handle ) { + if (!PC_Script_Parse(handle, &item->mouseExitText)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_action( itemDef_t *item, int handle ) { + if (!PC_Script_Parse(handle, &item->action)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_special( itemDef_t *item, int handle ) { + if (!PC_Float_Parse(handle, &item->special)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_cvarTest( itemDef_t *item, int handle ) { + if (!PC_String_Parse(handle, &item->cvarTest)) { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_cvar( itemDef_t *item, int handle ) +{ + Item_ValidateTypeData(item); + if (!PC_String_Parse(handle, &item->cvar)) + { + return qfalse; + } + + if ( item->typeData) + { + editFieldDef_t *editPtr; + + 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: + editPtr = (editFieldDef_t*)item->typeData; + editPtr->minVal = -1; + editPtr->maxVal = -1; + editPtr->defVal = -1; + break; + } + } + return qtrue; +} + +qboolean ItemParse_font( itemDef_t *item, int handle ) +{ + Item_ValidateTypeData(item); + if (!PC_Int_Parse(handle, &item->iMenuFont)) + { + return qfalse; + } + return qtrue; +} + + +qboolean ItemParse_maxChars( itemDef_t *item, int handle ) { + editFieldDef_t *editPtr; + int maxChars; + + Item_ValidateTypeData(item); + if (!item->typeData) + return qfalse; + + if (!PC_Int_Parse(handle, &maxChars)) { + return qfalse; + } + editPtr = (editFieldDef_t*)item->typeData; + editPtr->maxChars = maxChars; + return qtrue; +} + +qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle ) { + editFieldDef_t *editPtr; + int maxChars; + + Item_ValidateTypeData(item); + if (!item->typeData) + return qfalse; + + if (!PC_Int_Parse(handle, &maxChars)) { + return qfalse; + } + editPtr = (editFieldDef_t*)item->typeData; + editPtr->maxPaintChars = maxChars; + return qtrue; +} + +qboolean ItemParse_maxLineChars( itemDef_t *item, int handle ) +{ + textScrollDef_t *scrollPtr; + int maxChars; + + Item_ValidateTypeData(item); + if (!item->typeData) + return qfalse; + + if (!PC_Int_Parse(handle, &maxChars)) + { + return qfalse; + } + + scrollPtr = (textScrollDef_t*)item->typeData; + scrollPtr->maxLineChars = maxChars; + + return qtrue; +} + +qboolean ItemParse_lineHeight( itemDef_t *item, int handle ) +{ + textScrollDef_t *scrollPtr; + int height; + + Item_ValidateTypeData(item); + if (!item->typeData) + return qfalse; + + if (!PC_Int_Parse(handle, &height)) + { + return qfalse; + } + + scrollPtr = (textScrollDef_t*)item->typeData; + scrollPtr->lineHeight = height; + + return qtrue; +} + +qboolean ItemParse_cvarFloat( itemDef_t *item, int handle ) { + editFieldDef_t *editPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + return qfalse; + editPtr = (editFieldDef_t*)item->typeData; + if (PC_String_Parse(handle, &item->cvar) && + PC_Float_Parse(handle, &editPtr->defVal) && + PC_Float_Parse(handle, &editPtr->minVal) && + PC_Float_Parse(handle, &editPtr->maxVal)) { + return qtrue; + } + return qfalse; +} + +char currLanguage[32][128]; +static const char languageString[32] = "@MENUS_MYLANGUAGE"; + +qboolean ItemParse_cvarStrList( itemDef_t *item, int handle ) { + pc_token_t 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 (!trap_PC_ReadToken(handle, &token)) + { + return qfalse; + } + + if (!Q_stricmp(token.string,"feeder") && item->special == FEEDER_PLAYER_SPECIES) + { +#ifndef CGAME + 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 + } +#endif + return qtrue; + } + // languages + if (!Q_stricmp(token.string,"feeder") && item->special == FEEDER_LANGUAGES) + { +#ifndef CGAME + for (; multiPtr->count < uiInfo.languageCount; multiPtr->count++) + { + // The displayed text + trap_GetLanguageName( (const int) multiPtr->count,(char *) currLanguage[multiPtr->count] ); // eg "English" + multiPtr->cvarList[multiPtr->count] = languageString; + // The cvar value that goes into se_language + trap_GetLanguageName( (const int) multiPtr->count,(char *) currLanguage[multiPtr->count] ); + multiPtr->cvarStr[multiPtr->count] = currLanguage[multiPtr->count]; + } +#endif + return qtrue; + } + + if (*token.string != '{') { + return qfalse; + } + + pass = 0; + while ( 1 ) { + char* psString; + +// if (!trap_PC_ReadToken(handle, &token)) { +// PC_SourceError(handle, "end of file inside menu item\n"); +// return qfalse; +// } + + if (!PC_String_Parse(handle, (const char **)&psString)) + { + PC_SourceError(handle, "end of file inside menu item\n"); + return qfalse; + } + + //a normal StringAlloc ptr + if ((int)psString > 0) + { + if (*psString == '}') { + return qtrue; + } + + if (*psString == ',' || *psString == ';') { + continue; + } + } + + if (pass == 0) { + multiPtr->cvarList[multiPtr->count] = psString; + pass = 1; + } else { + multiPtr->cvarStr[multiPtr->count] = psString; + pass = 0; + multiPtr->count++; + if (multiPtr->count >= MAX_MULTI_CVARS) { + return qfalse; + } + } + + } + return qfalse; // bk001205 - LCC missing return value +} + +qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle ) +{ + pc_token_t token; + multiDef_t *multiPtr; + + Item_ValidateTypeData(item); + if (!item->typeData) + { + return qfalse; + } + + multiPtr = (multiDef_t*)item->typeData; + multiPtr->count = 0; + multiPtr->strDef = qfalse; + + if (!trap_PC_ReadToken(handle, &token)) + { + return qfalse; + } + + if (*token.string != '{') + { + return qfalse; + } + + while ( 1 ) + { + char* string; + + if ( !PC_String_Parse ( handle, (const char **)&string ) ) + { + PC_SourceError(handle, "end of file inside menu item\n"); + return qfalse; + } + + //a normal StringAlloc ptr + if ((int)string > 0) + { + if (*string == '}') + { + return qtrue; + } + + if (*string == ',' || *string == ';') + { + continue; + } + } + + multiPtr->cvarList[multiPtr->count] = string; + if (!PC_Float_Parse(handle, &multiPtr->cvarValue[multiPtr->count])) + { + return qfalse; + } + + multiPtr->count++; + if (multiPtr->count >= MAX_MULTI_CVARS) + { + return qfalse; + } + + } + return qfalse; // bk001205 - LCC missing return value +} + + + +qboolean ItemParse_addColorRange( itemDef_t *item, int handle ) { + colorRangeDef_t color; + + if (PC_Float_Parse(handle, &color.low) && + PC_Float_Parse(handle, &color.high) && + PC_Color_Parse(handle, &color.color) ) { + if (item->numColors < MAX_COLOR_RANGES) { + memcpy(&item->colorRanges[item->numColors], &color, sizeof(color)); + item->numColors++; + } + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle ) { + int i; + if (!PC_Int_Parse(handle, &i)) { + return qfalse; + } + item->window.ownerDrawFlags |= i; + return qtrue; +} + +qboolean ItemParse_enableCvar( itemDef_t *item, int handle ) { + if (PC_Script_Parse(handle, &item->enableCvar)) { + item->cvarFlags = CVAR_ENABLE; + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_disableCvar( itemDef_t *item, int handle ) { + if (PC_Script_Parse(handle, &item->enableCvar)) { + item->cvarFlags = CVAR_DISABLE; + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_showCvar( itemDef_t *item, int handle ) { + if (PC_Script_Parse(handle, &item->enableCvar)) { + item->cvarFlags = CVAR_SHOW; + return qtrue; + } + return qfalse; +} + +qboolean ItemParse_hideCvar( itemDef_t *item, int handle ) { + if (PC_Script_Parse(handle, &item->enableCvar)) { + item->cvarFlags = CVAR_HIDE; + return qtrue; + } + return qfalse; +} + +/* +=============== +ItemParse_align +=============== +*/ +qboolean ItemParse_Appearance_slot( itemDef_t *item, int handle ) +{ + if (!PC_Int_Parse(handle, &item->appearanceSlot)) + { + return qfalse; + } + return qtrue; +} + +qboolean ItemParse_isSaber( itemDef_t *item, int handle ) +{ +#ifndef CGAME + + int i; + + if (PC_Int_Parse(handle, &i)) + { + if ( i ) + { + item->flags |= ITF_ISSABER; + UI_CacheSaberGlowGraphics(); + if ( !ui_saber_parms_parsed ) + { + UI_SaberLoadParms(); + } + } + else + { + item->flags &= ~ITF_ISSABER; + } + + return qtrue; + } +#endif + return qfalse; +} + +//extern void UI_SaberLoadParms( void ); +//extern qboolean ui_saber_parms_parsed; +//extern void UI_CacheSaberGlowGraphics( void ); + +qboolean ItemParse_isSaber2( itemDef_t *item, int handle ) +{ +#ifndef CGAME + int i; + if (PC_Int_Parse(handle, &i)) + { + if ( i ) + { + item->flags |= ITF_ISSABER2; + UI_CacheSaberGlowGraphics(); + if ( !ui_saber_parms_parsed ) + { + UI_SaberLoadParms(); + } + } + else + { + item->flags &= ~ITF_ISSABER2; + } + + return qtrue; + } +#endif + + return qfalse; +} + +keywordHash_t itemParseKeywords[] = { + {"action", ItemParse_action, NULL }, + {"addColorRange", ItemParse_addColorRange, NULL }, + {"align", ItemParse_align, NULL }, + {"autowrapped", ItemParse_autowrapped, NULL }, + {"appearance_slot", ItemParse_Appearance_slot, NULL }, + {"asset_model", ItemParse_asset_model, NULL }, + {"asset_shader", ItemParse_asset_shader, NULL }, + {"backcolor", ItemParse_backcolor, NULL }, + {"background", ItemParse_background, NULL }, + {"border", ItemParse_border, NULL }, + {"bordercolor", ItemParse_bordercolor, NULL }, + {"bordersize", ItemParse_bordersize, NULL }, + {"cinematic", ItemParse_cinematic, NULL }, + {"columns", ItemParse_columns, NULL }, + {"cvar", ItemParse_cvar, NULL }, + {"cvarFloat", ItemParse_cvarFloat, NULL }, + {"cvarFloatList", ItemParse_cvarFloatList, NULL }, + {"cvarStrList", ItemParse_cvarStrList, NULL }, + {"cvarTest", ItemParse_cvarTest, NULL }, + {"desctext", ItemParse_descText, NULL }, + {"decoration", ItemParse_decoration, NULL }, + {"disableCvar", ItemParse_disableCvar, NULL }, + {"doubleclick", ItemParse_doubleClick, NULL }, + {"elementheight", ItemParse_elementheight, NULL }, + {"elementtype", ItemParse_elementtype, NULL }, + {"elementwidth", ItemParse_elementwidth, NULL }, + {"enableCvar", ItemParse_enableCvar, NULL }, + {"feeder", ItemParse_feeder, NULL }, + {"flag", ItemParse_flag, NULL }, + {"focusSound", ItemParse_focusSound, NULL }, + {"font", ItemParse_font, NULL }, + {"forecolor", ItemParse_forecolor, NULL }, + {"group", ItemParse_group, NULL }, + {"hideCvar", ItemParse_hideCvar, NULL }, + {"horizontalscroll", ItemParse_horizontalscroll, NULL }, + {"isCharacter", ItemParse_isCharacter, NULL }, + {"isSaber", ItemParse_isSaber, NULL }, + {"isSaber2", ItemParse_isSaber2, NULL }, + {"leaveFocus", ItemParse_leaveFocus, NULL }, + {"maxChars", ItemParse_maxChars, NULL }, + {"maxPaintChars", ItemParse_maxPaintChars, NULL }, + {"model_angle", ItemParse_model_angle, NULL }, + {"model_fovx", ItemParse_model_fovx, NULL }, + {"model_fovy", ItemParse_model_fovy, NULL }, + {"model_origin", ItemParse_model_origin, NULL }, + {"model_rotation", ItemParse_model_rotation, NULL }, + //rww - g2 begin + {"model_g2mins", ItemParse_model_g2mins, NULL }, + {"model_g2maxs", ItemParse_model_g2maxs, NULL }, + {"model_g2scale", ItemParse_model_g2scale, NULL }, + {"model_g2skin", ItemParse_model_g2skin, NULL }, + {"model_g2anim", ItemParse_model_g2anim, NULL }, + //rww - g2 end + {"mouseEnter", ItemParse_mouseEnter, NULL }, + {"mouseEnterText", ItemParse_mouseEnterText, NULL }, + {"mouseExit", ItemParse_mouseExit, NULL }, + {"mouseExitText", ItemParse_mouseExitText, NULL }, + {"name", ItemParse_name, NULL }, + {"notselectable", ItemParse_notselectable, NULL }, + {"onFocus", ItemParse_onFocus, NULL }, + {"outlinecolor", ItemParse_outlinecolor, NULL }, + {"ownerdraw", ItemParse_ownerdraw, NULL }, + {"ownerdrawFlag", ItemParse_ownerdrawFlag, NULL }, + {"rect", ItemParse_rect, NULL }, + {"rectcvar", ItemParse_rectcvar, NULL }, + {"showCvar", ItemParse_showCvar, NULL }, + {"special", ItemParse_special, NULL }, + {"style", ItemParse_style, NULL }, + {"text", ItemParse_text, NULL }, + {"textalign", ItemParse_textalign, NULL }, + {"textalignx", ItemParse_textalignx, NULL }, + {"textaligny", ItemParse_textaligny, NULL }, + {"textscale", ItemParse_textscale, NULL }, + {"textstyle", ItemParse_textstyle, NULL }, + {"text2", ItemParse_text2, NULL }, + {"text2alignx", ItemParse_text2alignx, NULL }, + {"text2aligny", ItemParse_text2aligny, NULL }, + {"type", ItemParse_type, NULL }, + {"visible", ItemParse_visible, NULL }, + {"wrapped", ItemParse_wrapped, NULL }, + + // Text scroll specific + {"maxLineChars", ItemParse_maxLineChars, NULL }, + {"lineHeight", ItemParse_lineHeight, NULL }, + {"invertyesno", ItemParse_invertyesno, NULL }, + //JLF MPMOVED + {"scrollhidden", ItemParse_scrollhidden, NULL }, + {"xoffset ", ItemParse_xoffset, NULL }, + //JLF end + + + + {0, 0, 0 } +}; + +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(int handle, itemDef_t *item) { + pc_token_t token; + keywordHash_t *key; + + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (*token.string != '{') { + return qfalse; + } + while ( 1 ) { + if (!trap_PC_ReadToken(handle, &token)) { + PC_SourceError(handle, "end of file inside menu item\n"); + return qfalse; + } + + if (*token.string == '}') { + return qtrue; + } + + key = KeywordHash_Find(itemParseKeywordHash, token.string); + if (!key) { + PC_SourceError(handle, "unknown menu item keyword %s", token.string); + continue; + } + if ( !key->func(item, handle) ) { + PC_SourceError(handle, "couldn't parse menu item keyword %s", token.string); + return qfalse; + } + } + return qfalse; // bk001205 - LCC missing return value +} + +static void Item_TextScroll_BuildLines ( itemDef_t* item ) +{ + char text[2048]; + +#if 1 + + // 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 == '@') // string reference + { + trap_SP_GetStringTextString( &psText[1], text, sizeof(text)); + psText = text; + } + + 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 = trap_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->iMenuFont ) >= iBoxWidth ) + { + // reached screen edge, so cap off string at bytepos after last good position... + // + if (uiLetter > 255 && bIsTrailingPunctuation && !trap_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 && !trap_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++; + } + +#else + // old version... + // + int width; + char* lineStart; + char* lineEnd; + float w; + float cw; + + textScrollDef_t* scrollPtr = (textScrollDef_t*) item->typeData; + + scrollPtr->iLineCount = 0; + width = scrollPtr->maxLineChars; + lineStart = (char*)item->text; + lineEnd = lineStart; + w = 0; + + // Keep going as long as there are more lines + while ( scrollPtr->iLineCount < MAX_TEXTSCROLL_LINES ) + { + // End of the road + if ( *lineEnd == '\0') + { + if ( lineStart < lineEnd ) + { + scrollPtr->pLines[ scrollPtr->iLineCount++ ] = lineStart; + } + + break; + } + + // Force a line end if its a '\n' + else if ( *lineEnd == '\n' ) + { + *lineEnd = '\0'; + scrollPtr->pLines[ scrollPtr->iLineCount++ ] = lineStart; + lineStart = lineEnd + 1; + lineEnd = lineStart; + w = 0; + continue; + } + + // Get the current character width + cw = DC->textWidth ( va("%c", *lineEnd), item->textscale, item->iMenuFont ); + + // Past the end of the boundary? + if ( w + cw > (item->window.rect.w - SCROLLBAR_SIZE - 10) ) + { + // Past the end so backtrack to the word boundary + while ( *lineEnd != ' ' && *lineEnd != '\t' && lineEnd > lineStart ) + { + lineEnd--; + } + + *lineEnd = '\0'; + scrollPtr->pLines[ scrollPtr->iLineCount++ ] = lineStart; + + // Skip any whitespaces + lineEnd++; + while ( (*lineEnd == ' ' || *lineEnd == '\t') && *lineEnd ) + { + lineEnd++; + } + + lineStart = lineEnd; + w = 0; + } + else + { + w += cw; + lineEnd++; + } + } +#endif +} + +// Item_InitControls +// init's special control types +void Item_InitControls(itemDef_t *item) +{ + if (item == NULL) + { + return; + } + + switch ( item->type ) + { + case 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; + } + + break; + } + } +} + +/* +=============== +Menu Keyword Parse functions +=============== +*/ + +qboolean MenuParse_font( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_String_Parse(handle, &menu->font)) { + return qfalse; + } + if (!DC->Assets.fontRegistered) { + //DC->registerFont(menu->font, 48, &DC->Assets.textFont); + DC->Assets.qhMediumFont = DC->RegisterFont(menu->font); + DC->Assets.fontRegistered = qtrue; + } + return qtrue; +} + +qboolean MenuParse_name( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_String_Parse(handle, &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; +} + +qboolean MenuParse_fullscreen( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_Int_Parse(handle, (int*) &menu->fullScreen)) { // bk001206 - cast qboolean + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_rect( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_Rect_Parse(handle, &menu->window.rect)) { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_style +================= +*/ +qboolean MenuParse_style( itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Int_Parse(handle, &menu->window.style)) + { + Com_Printf(S_COLOR_YELLOW "Unknown menu style value"); + return qfalse; + } + + return qtrue; +} + +qboolean MenuParse_visible( itemDef_t *item, int handle ) { + int i; + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Int_Parse(handle, &i)) { + return qfalse; + } + if (i) { + menu->window.flags |= WINDOW_VISIBLE; + } + return qtrue; +} + +qboolean MenuParse_onOpen( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_Script_Parse(handle, &menu->onOpen)) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_onClose( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_Script_Parse(handle, &menu->onClose)) { + return qfalse; + } + return qtrue; +} + +//JLFACCEPT MPMOVED +/* +================= +MenuParse_onAccept +================= +*/ +qboolean MenuParse_onAccept( itemDef_t *item, int handle ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Script_Parse(handle, &menu->onAccept)) + { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_onESC( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_Script_Parse(handle, &menu->onESC)) { + return qfalse; + } + return qtrue; +} + + + +qboolean MenuParse_border( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_Int_Parse(handle, &menu->window.border)) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_borderSize( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_Float_Parse(handle, &menu->window.borderSize)) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_backcolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &f)) { + return qfalse; + } + menu->window.backColor[i] = f; + } + return qtrue; +} + +/* +================= +MenuParse_descAlignment +================= +*/ +qboolean MenuParse_descAlignment( itemDef_t *item, int handle ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Int_Parse(handle, &menu->descAlignment)) + { + Com_Printf(S_COLOR_YELLOW "Unknown desc alignment value"); + return qfalse; + } + + return qtrue; +} + +/* +================= +MenuParse_descX +================= +*/ +qboolean MenuParse_descX( itemDef_t *item, int handle ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Int_Parse(handle, &menu->descX)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_descY +================= +*/ +qboolean MenuParse_descY( itemDef_t *item, int handle ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Int_Parse(handle, &menu->descY)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_descScale +================= +*/ +qboolean MenuParse_descScale( itemDef_t *item, int handle) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Float_Parse(handle, &menu->descScale)) + { + return qfalse; + } + return qtrue; +} + +/* +================= +MenuParse_descColor +================= +*/ +qboolean MenuParse_descColor( itemDef_t *item, int handle) +{ + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) + { + if (!PC_Float_Parse(handle, &f)) + { + return qfalse; + } + menu->descColor[i] = f; + } + return qtrue; +} + +qboolean MenuParse_forecolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &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; +} + +qboolean MenuParse_bordercolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &f)) { + return qfalse; + } + menu->window.borderColor[i] = f; + } + return qtrue; +} + +qboolean MenuParse_focuscolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &f)) { + return qfalse; + } + menu->focusColor[i] = f; + } + return qtrue; +} + +qboolean MenuParse_disablecolor( itemDef_t *item, int handle ) { + int i; + float f; + menuDef_t *menu = (menuDef_t*)item; + for (i = 0; i < 4; i++) { + if (!PC_Float_Parse(handle, &f)) { + return qfalse; + } + menu->disableColor[i] = f; + } + return qtrue; +} + + +qboolean MenuParse_outlinecolor( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (!PC_Color_Parse(handle, &menu->window.outlineColor)){ + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_background( itemDef_t *item, int handle ) { + pc_token_t token; + menuDef_t *menu = (menuDef_t*)item; + + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + menu->window.background = DC->registerShaderNoMip(token.string); + return qtrue; +} + +qboolean MenuParse_cinematic( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_String_Parse(handle, &menu->window.cinematicName)) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle ) { + int i; + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Int_Parse(handle, &i)) { + return qfalse; + } + menu->window.ownerDrawFlags |= i; + return qtrue; +} + +qboolean MenuParse_ownerdraw( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Int_Parse(handle, &menu->window.ownerDraw)) { + return qfalse; + } + return qtrue; +} + + +// decoration +qboolean MenuParse_popup( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + menu->window.flags |= WINDOW_POPUP; + return qtrue; +} + + +qboolean MenuParse_outOfBounds( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + menu->window.flags |= WINDOW_OOB_CLICK; + return qtrue; +} + +qboolean MenuParse_soundLoop( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_String_Parse(handle, &menu->soundName)) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_fadeClamp( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Float_Parse(handle, &menu->fadeClamp)) { + return qfalse; + } + return qtrue; +} + +qboolean MenuParse_fadeAmount( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Float_Parse(handle, &menu->fadeAmount)) { + return qfalse; + } + return qtrue; +} + + +qboolean MenuParse_fadeCycle( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Int_Parse(handle, &menu->fadeCycle)) { + return qfalse; + } + return qtrue; +} + + +qboolean MenuParse_itemDef( itemDef_t *item, int handle ) { + menuDef_t *menu = (menuDef_t*)item; + if (menu->itemCount < MAX_MENUITEMS) { + menu->items[menu->itemCount] = (itemDef_t *) UI_Alloc(sizeof(itemDef_t)); + Item_Init(menu->items[menu->itemCount]); + if (!Item_Parse(handle, menu->items[menu->itemCount])) { + return qfalse; + } + Item_InitControls(menu->items[menu->itemCount]); + menu->items[menu->itemCount++]->parent = menu; + } + return qtrue; +} +/* +================= +MenuParse_focuscolor +================= +*/ +qboolean MenuParse_appearanceIncrement( itemDef_t *item, int handle ) +{ + menuDef_t *menu = (menuDef_t*)item; + + if (!PC_Float_Parse(handle, &menu->appearanceIncrement)) + { + return qfalse; + } + return qtrue; +} + +keywordHash_t menuParseKeywords[] = { + {"appearanceIncrement", MenuParse_appearanceIncrement, NULL }, + {"backcolor", MenuParse_backcolor, NULL }, + {"background", MenuParse_background, NULL }, + {"border", MenuParse_border, NULL }, + {"bordercolor", MenuParse_bordercolor, NULL }, + {"borderSize", MenuParse_borderSize, NULL }, + {"cinematic", MenuParse_cinematic, NULL }, + {"descAlignment", MenuParse_descAlignment,NULL }, + {"desccolor", MenuParse_descColor, NULL }, + {"descX", MenuParse_descX, NULL }, + {"descY", MenuParse_descY, NULL }, + {"descScale", MenuParse_descScale, NULL }, + {"disablecolor", MenuParse_disablecolor, NULL }, + {"fadeAmount", MenuParse_fadeAmount, NULL }, + {"fadeClamp", MenuParse_fadeClamp, NULL }, + {"fadeCycle", MenuParse_fadeCycle, NULL }, + {"focuscolor", MenuParse_focuscolor, NULL }, + {"font", MenuParse_font, NULL }, + {"forecolor", MenuParse_forecolor, NULL }, + {"fullscreen", MenuParse_fullscreen, NULL }, + {"itemDef", MenuParse_itemDef, NULL }, + {"name", MenuParse_name, NULL }, + {"onClose", MenuParse_onClose, NULL }, + //JLFACCEPT MPMOVED + {"onAccept", MenuParse_onAccept, NULL }, + + {"onESC", MenuParse_onESC, NULL }, + {"outOfBoundsClick", MenuParse_outOfBounds, NULL }, + {"onOpen", MenuParse_onOpen, NULL }, + {"outlinecolor", MenuParse_outlinecolor, NULL }, + {"ownerdraw", MenuParse_ownerdraw, NULL }, + {"ownerdrawFlag", MenuParse_ownerdrawFlag,NULL }, + {"popup", MenuParse_popup, NULL }, + {"rect", MenuParse_rect, NULL }, + {"soundLoop", MenuParse_soundLoop, NULL }, + {"style", MenuParse_style, NULL }, + {"visible", MenuParse_visible, NULL }, + {0, 0, 0 } +}; + +keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE]; + +/* +=============== +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]); + } +} + +/* +=============== +Menu_Parse +=============== +*/ +qboolean Menu_Parse(int handle, menuDef_t *menu) { + pc_token_t token; + keywordHash_t *key; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (*token.string != '{') { + return qfalse; + } + + while ( 1 ) { + if (!trap_PC_ReadToken(handle, &token)) { + PC_SourceError(handle, "end of file inside menu\n"); + return qfalse; + } + + if (*token.string == '}') { + return qtrue; + } + + key = KeywordHash_Find(menuParseKeywordHash, token.string); + if (!key) { + PC_SourceError(handle, "unknown menu keyword %s", token.string); + continue; + } + if ( !key->func((itemDef_t*)menu, handle) ) { + PC_SourceError(handle, "couldn't parse menu keyword %s", token.string); + return qfalse; + } + } + return qfalse; // bk001205 - LCC missing return value +} + +/* +=============== +Menu_New +=============== +*/ +void Menu_New(int handle) { + menuDef_t *menu = &Menus[menuCount]; + + if (menuCount < MAX_MENUS) { + Menu_Init(menu); + if (Menu_Parse(handle, menu)) { + Menu_PostParse(menu); + menuCount++; + } + } +} + +int Menu_Count() { + return menuCount; +} + +void Menu_PaintAll() { + int i; + if (captureFunc) { + captureFunc(captureData); + } + + for (i = 0; i < Menu_Count(); i++) { + Menu_Paint(&Menus[i], qfalse); + } + + if (debugMode) { + vec4_t v = {1, 1, 1, 1}; + DC->drawText(5, 25, .75, v, va("fps: %f", DC->FPS), 0, 0, 0, 0); + DC->drawText(5, 45, .75, v, va("x: %d y:%d", DC->cursorx,DC->cursory), 0, 0, 0, 0); + } +} + +void Menu_Reset() { + menuCount = 0; +} + +displayContextDef_t *Display_GetContext() { + return DC; +} + +void *Display_CaptureItem(int x, int y) { + int i; + + for (i = 0; i < menuCount; i++) { + // turn off focus each item + // menu->items[i].window.flags &= ~WINDOW_HASFOCUS; + if (Rect_ContainsPoint(&Menus[i].window.rect, x, y)) { + return &Menus[i]; + } + } + return NULL; +} + + +// FIXME: +qboolean Display_MouseMove(void *p, int x, int y) { + +//JLFMOUSE AGAIN I THINK THIS SHOULD BE MOOT +#ifdef _XBOX + return qtrue; +#endif + //END JLF + 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; + +} + +int Display_CursorType(int x, int y) { + int i; + for (i = 0; i < menuCount; i++) { + rectDef_t r2; + r2.x = Menus[i].window.rect.x - 3; + r2.y = Menus[i].window.rect.y - 3; + r2.w = r2.h = 7; + if (Rect_ContainsPoint(&r2, x, y)) { + return CURSOR_SIZER; + } + } + return CURSOR_ARROW; +} + + +void Display_HandleKey(int key, qboolean down, int x, int y) { + menuDef_t *menu = (menuDef_t *) Display_CaptureItem(x, y); + if (menu == NULL) { + menu = Menu_GetFocused(); + } + if (menu) { + Menu_HandleKey(menu, key, down ); + } +} + +static void Window_CacheContents(windowDef_t *window) { + if (window) { + if (window->cinematicName) { + int cin = DC->playCinematic(window->cinematicName, 0, 0, 0, 0); + DC->stopCinematic(cin); + } + } +} + + +static void Item_CacheContents(itemDef_t *item) { + if (item) { + Window_CacheContents(&item->window); + } + +} + +static void Menu_CacheContents(menuDef_t *menu) { + if (menu) { + int i; + Window_CacheContents(&menu->window); + for (i = 0; i < menu->itemCount; i++) { + Item_CacheContents(menu->items[i]); + } + + if (menu->soundName && *menu->soundName) { + DC->registerSound(menu->soundName); + } + } + +} + +void Display_CacheAll() { + int i; + for (i = 0; i < menuCount; i++) { + Menu_CacheContents(&Menus[i]); + } +} + + +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; +} + +//JLF DEMOCODE MPMOVED +#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 ); +#ifndef CGAME + trap_Key_ClearStates(); +#endif + trap_Cvar_Set( "cl_paused", "0" ); + +// g_demoStartFade = 0; +// g_demoStartTransition = 0; +} + + + +const char *attractMovieNames[] = { + "jk1", + "jk2", + "jk3", + "jk4", + "jk5", +}; + +extern int trap_Milliseconds( void ); + +const int numAttractMovies = sizeof(attractMovieNames) / sizeof(attractMovieNames[0]); +static int curAttractMovie = 0; + +void G_DemoFrame() +{ + bool keypressed = false; + if (g_runningDemo) + { + while (!keypressed) + keypressed = CIN_PlayAllFrames( "atvi.bik", 0, 0, 640, 480, 0, true ); + G_DemoEnd(); + + + + } + else + { + 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 = trap_Milliseconds(); + else if (g_demoLastKeypress + DEMO_TIME_MAX < trap_Milliseconds()) + G_DemoStart(); + } + else + { + g_demoLastKeypress = trap_Milliseconds(); + } + } +} + +void G_DemoKeypress() +{ + g_demoLastKeypress = trap_Milliseconds(); + +//JLF moved +// g_demoLastKeypress = Sys_Milliseconds(); + +} + + +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 ); +#ifndef CGAME + trap_Key_ClearStates(); +#endif + trap_Cvar_Set( "cl_paused", "0" ); + +// g_demoStartFade = 0; +// g_demoStartTransition = 0; +// g_demoLastKeypress = 0; +} + +void PlayDemo() +{ +// bool keypressed = false; + G_DemoStart(); + CIN_PlayAllFrames( attractMovieNames[curAttractMovie], 0, 0, 640, 480, 0, true ); + curAttractMovie = (curAttractMovie + 1) % numAttractMovies; +// while (!keypressed) +// keypressed = CIN_PlayAllFrames( "atvi.bik", 0, 0, 640, 480, 0, true ); + G_DemoEnd(); +} + +void UpdateDemoTimer() +{ + g_demoLastKeypress = trap_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 = trap_Milliseconds(); + else if (g_demoLastKeypress + DEMO_TIME_MAX < trap_Milliseconds()) + return true; + } + return false; +} + +//END DEMOCODE + + +#endif // _XBOX + + + + + + + + + + + +#include "../namespace_end.h" diff --git a/codemp/ui/ui_shared.h b/codemp/ui/ui_shared.h new file mode 100644 index 0000000..e631e45 --- /dev/null +++ b/codemp/ui/ui_shared.h @@ -0,0 +1,622 @@ +#ifndef __UI_SHARED_H +#define __UI_SHARED_H + + +#include "../game/q_shared.h" +#include "../cgame/tr_types.h" +#include "keycodes.h" + +#include "../../ui/menudef.h" + +#define MAX_MENUNAME 32 +#define MAX_ITEMTEXT 64 +#define MAX_ITEMACTION 64 +#define MAX_MENUDEFFILE 4096 +#define MAX_MENUFILE 32768 +#define MAX_MENUS 64 +#define MAX_MENUITEMS 256 +#define MAX_COLOR_RANGES 10 +#define MAX_OPEN_MENUS 16 +#define MAX_TEXTSCROLL_LINES 256 + +#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_* + +//JLF +#define WINDOW_INTRANSITIONMODEL 0x04000000 // delayed script waiting to run + + +// CGAME cursor type bits +#define CURSOR_NONE 0x00000001 +#define CURSOR_ARROW 0x00000002 +#define CURSOR_SIZER 0x00000004 + +#ifdef _XBOX + +#ifdef CGAME +#define STRING_POOL_SIZE 32*1024 +#else +#define STRING_POOL_SIZE 128*1024 +#endif + +#else + +#ifdef CGAME +#define STRING_POOL_SIZE 128*1024 +#else +#define STRING_POOL_SIZE 384*1024 +#endif + +#endif + +#define MAX_STRING_HANDLES 4096 +#define MAX_SCRIPT_ARGS 12 +#define MAX_EDITFIELD 256 + +#define ART_FX_BASE "menu/art/fx_base" +#define ART_FX_BLUE "menu/art/fx_blue" +#define ART_FX_CYAN "menu/art/fx_cyan" +#define ART_FX_GREEN "menu/art/fx_grn" +#define ART_FX_RED "menu/art/fx_red" +#define ART_FX_TEAL "menu/art/fx_teal" +#define ART_FX_WHITE "menu/art/fx_white" +#define ART_FX_YELLOW "menu/art/fx_yel" +#define ART_FX_ORANGE "menu/art/fx_orange" +#define ART_FX_PURPLE "menu/art/fx_purple" + +#define ASSET_GRADIENTBAR "ui/assets/gradientbar2.tga" +#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" +#define ASSET_SLIDER_BAR "menu/new/slider" +#define ASSET_SLIDER_THUMB "menu/new/sliderthumb" +#define SCROLLBAR_SIZE 16.0 +#define SLIDER_WIDTH 96.0 +#define SLIDER_HEIGHT 16.0 +#define SLIDER_THUMB_WIDTH 12.0 +#define SLIDER_THUMB_HEIGHT 20.0 +#define NUM_CROSSHAIRS 9 + +typedef struct { + const char *command; + const char *args[MAX_SCRIPT_ARGS]; +} scriptDef_t; + + +typedef struct { + float x; // horiz position + float y; // vert position + float w; // width + float h; // height; +} rectDef_t; + +typedef rectDef_t Rectangle; + +// FIXME: do something to separate text vs window stuff +typedef struct { + Rectangle rect; // client coord rectangle + Rectangle rectClient; // screen coord rectangle + const char *name; // + const 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 + Rectangle rectEffects; // for various effects + Rectangle rectEffects2; // for various effects + int offsetTime; // time based value for various effects + int nextTime; // time next effect should cycle + 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; + +// 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; +} 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 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; + +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 + vec3_t g2scale; //optional + int g2skin; //optional + int g2anim; //optional + //JLF +//Transition extras + vec3_t g2mins2, g2maxs2, g2minsEffect, g2maxsEffect; + float fov_x2, fov_y2, fov_Effectx, fov_Effecty; +} modelDef_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; + +#define ITEM_ALIGN_LEFT 0 // left alignment +#define ITEM_ALIGN_CENTER 1 // center alignment +#define ITEM_ALIGN_RIGHT 2 // right alignment + +#define CVAR_ENABLE 0x00000001 +#define CVAR_DISABLE 0x00000002 +#define CVAR_SHOW 0x00000004 +#define CVAR_HIDE 0x00000008 + +#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 + Rectangle 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 x coord + float textscale; // scale percentage from 72pts + int textStyle; // ( optional ) style, normal and shadowed are it for now + const char *text; // display text + const char *text2; // display text, 2nd line + float text2alignx; // ( optional ) text2 alignment x coord + float text2aligny; // ( optional ) text2 alignment y coord + void *parent; // menu owner + qhandle_t asset; // handle to asset + void *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 + 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; + 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 + const char *descText; // Description text + int appearanceSlot; // order of appearance + int iMenuFont; // FONT_SMALL,FONT_MEDIUM,FONT_LARGE // changed from 'font' so I could see what didn't compile, and differentiate between font handles returned from RegisterFont -ste + qboolean disabled; // Does this item ignore mouse and keyboard focus + 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 + const char *onAccept; // run when menu is closed with acceptance + + const char *onESC; // run when the menu is closed + 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 + 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 + float appearanceTime; // when next item should appear + int appearanceCnt; // current item displayed + float appearanceIncrement; // +} menuDef_t; + +typedef struct { + const char *fontStr; + const char *cursorStr; + const char *gradientStr; + qhandle_t qhSmallFont; + qhandle_t qhSmall2Font; + qhandle_t qhMediumFont; + qhandle_t qhBigFont; + 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; + float fadeClamp; + int fadeCycle; + float fadeAmount; + float shadowX; + float shadowY; + vec4_t shadowColor; + float shadowFadeClamp; + qboolean fontRegistered; + + qhandle_t needPass; + qhandle_t noForce; + qhandle_t forceRestrict; + qhandle_t saberOnly; + qhandle_t trueJedi; + + sfxHandle_t moveRollSound; + sfxHandle_t moveJumpSound; + sfxHandle_t datapadmoveSaberSound1; + sfxHandle_t datapadmoveSaberSound2; + sfxHandle_t datapadmoveSaberSound3; + sfxHandle_t datapadmoveSaberSound4; + sfxHandle_t datapadmoveSaberSound5; + sfxHandle_t datapadmoveSaberSound6; + + // player settings + qhandle_t fxBasePic; + qhandle_t fxPic[7]; + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + +} cachedAssets_t; + +typedef struct +{ + const char *name; + qboolean (*handler) (itemDef_t *item, char** args); +} commandDef_t; + +typedef struct { + qhandle_t (*registerShaderNoMip) (const char *p); + void (*setColor) (const vec4_t v); + void (*drawHandlePic) (float x, float y, float w, float h, qhandle_t asset); + void (*drawStretchPic) (float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); + void (*drawText) (float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style, int iMenuFont); + int (*textWidth) (const char *text, float scale, int iMenuFont); + int (*textHeight) (const char *text, float scale, int iMenuFont); + qhandle_t (*registerModel) (const char *p); + void (*modelBounds) (qhandle_t model, vec3_t min, vec3_t max); + void (*fillRect) ( float x, float y, float w, float h, const vec4_t color); + 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 (*drawTopBottom) (float x, float y, float w, float h, float size); + void (*clearScene) (); + void (*addRefEntityToScene) (const refEntity_t *re ); + void (*renderScene) ( const refdef_t *fd ); + + 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 (*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 iMenuFont); + float (*getValue) (int ownerDraw); + qboolean (*ownerDrawVisible) (int flags); + void (*runScript)(char **p); + qboolean (*deferScript)(char **p); + void (*getTeamColor)(vec4_t *color); + void (*getCVarString)(const char *cvar, char *buffer, int bufsize); + float (*getCVarValue)(const char *cvar); + void (*setCVar)(const char *cvar, const char *value); + void (*drawTextWithCursor)(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style, int iFontIndex); + void (*setOverstrikeMode)(qboolean b); + qboolean (*getOverstrikeMode)(); + void (*startLocalSound)( sfxHandle_t sfx, int channelNum ); + qboolean (*ownerDrawHandleKey)(int ownerDraw, int flags, float *special, int key); + int (*feederCount)(float feederID); + const char *(*feederItemText)(float feederID, int index, int column, qhandle_t *handle1, qhandle_t *handle2, qhandle_t *handle3); + qhandle_t (*feederItemImage)(float feederID, int index); + qboolean (*feederSelection)(float feederID, int index, itemDef_t *item); + void (*keynumToStringBuf)( int keynum, char *buf, int buflen ); + void (*getBindingBuf)( int keynum, char *buf, int buflen ); + void (*setBinding)( int keynum, const char *binding ); + void (*executeText)(int exec_when, const char *text ); + void (*Error)(int level, const char *error, ...); + void (*Print)(const char *msg, ...); + void (*Pause)(qboolean b); + int (*ownerDrawWidth)(int ownerDraw, float scale); + sfxHandle_t (*registerSound)(const char *name); + void (*startBackgroundTrack)( const char *intro, const char *loop, qboolean bReturnWithoutStarting); + void (*stopBackgroundTrack)(); + int (*playCinematic)(const char *name, float x, float y, float w, float h); + void (*stopCinematic)(int handle); + void (*drawCinematic)(int handle, float x, float y, float w, float h); + void (*runCinematicFrame)(int handle); + + float yscale; + float xscale; + float bias; + int realTime; + int frameTime; + int cursorx; + int cursory; + qboolean debug; + + cachedAssets_t Assets; + + glconfig_t glconfig; + qhandle_t whiteShader; + qhandle_t gradientImage; + qhandle_t cursor; + float FPS; + +} displayContextDef_t; + +#include "../namespace_begin.h" + +const char *String_Alloc(const char *p); +void String_Init(); +void String_Report(); +void Init_Display(displayContextDef_t *dc); +void Display_ExpandMacros(char * buff); +void Menu_Init(menuDef_t *menu); +void Item_Init(itemDef_t *item); +void Menu_PostParse(menuDef_t *menu); +menuDef_t *Menu_GetFocused(); +void Menu_HandleKey(menuDef_t *menu, int key, qboolean down); +void Menu_HandleMouseMove(menuDef_t *menu, float x, float y); +void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down); +qboolean Float_Parse(char **p, float *f); +qboolean Color_Parse(char **p, vec4_t *c); +qboolean Int_Parse(char **p, int *i); +qboolean Rect_Parse(char **p, rectDef_t *r); +qboolean String_Parse(char **p, const char **out); +qboolean Script_Parse(char **p, const char **out); +qboolean PC_Float_Parse(int handle, float *f); +qboolean PC_Color_Parse(int handle, vec4_t *c); +qboolean PC_Int_Parse(int handle, int *i); +qboolean PC_Rect_Parse(int handle, rectDef_t *r); +qboolean PC_String_Parse(int handle, const char **out); +qboolean PC_Script_Parse(int handle, const char **out); +int Menu_Count(); +void Menu_New(int handle); +void Menu_PaintAll(); +menuDef_t *Menus_ActivateByName(const char *p); +void Menu_Reset(); +qboolean Menus_AnyFullScreenVisible(); +void Menus_Activate(menuDef_t *menu); +itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p); + +displayContextDef_t *Display_GetContext(); +void *Display_CaptureItem(int x, int y); +qboolean Display_MouseMove(void *p, int x, int y); +int Display_CursorType(int x, int y); +qboolean Display_KeyBindPending(); +void Menus_OpenByName(const char *p); +menuDef_t *Menus_FindByName(const char *p); +void Menus_ShowByName(const char *p); +void Menus_CloseByName(const char *p); +void Display_HandleKey(int key, qboolean down, int x, int y); +void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t); +void Menus_CloseAll(); +void Menu_Paint(menuDef_t *menu, qboolean forcePaint); +void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name); +void Display_CacheAll(); +void Menu_SetItemBackground(const menuDef_t *menu,const char *itemName, const char *background); + +void *UI_Alloc( int size ); +void UI_InitMemory( void ); +qboolean UI_OutOfMemory(); + +void Controls_GetConfig( void ); +void Controls_SetConfig(qboolean restart); + + +int trap_PC_AddGlobalDefine ( char *define ); +int trap_PC_LoadSource ( const char *filename ); +int trap_PC_FreeSource ( int handle ); +int trap_PC_ReadToken ( int handle, pc_token_t *pc_token ); +int trap_PC_SourceFileAndLine ( int handle, char *filename, int *line ); +int trap_PC_LoadGlobalDefines ( const char* filename ); +void trap_PC_RemoveAllGlobalDefines ( void ); + +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 int trap_AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation ); + +int trap_SP_GetStringTextString(const char *text, char *buffer, int bufferLength); +int trap_SP_GetNumLanguages( void ); +void trap_GetLanguageName( const int languageIndex, char *buffer ); + +//these traps must exist both on the cgame and ui +/* +Ghoul2 Insert Start +*/ +// UI specific API access +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 ); + +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); +qboolean trap_G2API_AttachG2Model(void *ghoul2From, int modelIndexFrom, void *ghoul2To, int toBoltIndex, int toModel); + + +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); + +int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName); +//qboolean trap_G2API_RemoveBolt(void *ghoul2, int index); +void trap_G2API_SetBoltInfo(void *ghoul2, int modelIndex, int boltInfo); +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); + +int trap_G2API_GetTime(void); +void trap_G2API_SetTime(int time, int clock); + +void trap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params); +void trap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params); + +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); + +void trap_G2API_GetSurfaceName(void *ghoul2, int surfNumber, int modelIndex, char *fillBuf); + +#include "../namespace_end.h" + +/* +Ghoul2 Insert End +*/ +#endif diff --git a/codemp/ui/ui_syscalls.c b/codemp/ui/ui_syscalls.c new file mode 100644 index 0000000..cbbda6c --- /dev/null +++ b/codemp/ui/ui_syscalls.c @@ -0,0 +1,651 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "ui_local.h" + +// this file is only included when building a dll +// syscalls.asm is included instead when building a qvm + +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_Print( const char *string ) { + syscall( UI_PRINT, string ); +} + +void trap_Error( const char *string ) { + syscall( UI_ERROR, string ); +} + +int trap_Milliseconds( void ) { + return syscall( UI_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) { + syscall( UI_CVAR_REGISTER, cvar, var_name, value, flags ); +} + +void trap_Cvar_Update( vmCvar_t *cvar ) { + syscall( UI_CVAR_UPDATE, cvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( UI_CVAR_SET, var_name, value ); +} + +float trap_Cvar_VariableValue( const char *var_name ) { + int temp; + temp = syscall( UI_CVAR_VARIABLEVALUE, var_name ); + return (*(float*)&temp); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +void trap_Cvar_SetValue( const char *var_name, float value ) { + syscall( UI_CVAR_SETVALUE, var_name, PASSFLOAT( value ) ); +} + +void trap_Cvar_Reset( const char *name ) { + syscall( UI_CVAR_RESET, name ); +} + +void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ) { + syscall( UI_CVAR_CREATE, var_name, var_value, flags ); +} + +void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ) { + syscall( UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize ); +} + +int trap_Argc( void ) { + return syscall( UI_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( UI_ARGV, n, buffer, bufferLength ); +} + +void trap_Cmd_ExecuteText( int exec_when, const char *text ) { + syscall( UI_CMD_EXECUTETEXT, exec_when, text ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( UI_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( UI_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( UI_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( UI_FS_FCLOSEFILE, f ); +} + +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + return syscall( UI_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + return syscall( UI_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { + return syscall( UI_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterFont( const char *fontName ) +{ + return syscall( UI_R_REGISTERFONT, fontName); +} + +int trap_R_Font_StrLenPixels(const char *text, const int iFontIndex, const float scale) +{ + return syscall( UI_R_FONT_STRLENPIXELS, text, iFontIndex, PASSFLOAT(scale)); +} + +int trap_R_Font_StrLenChars(const char *text) +{ + return syscall( UI_R_FONT_STRLENCHARS, text); +} + +int trap_R_Font_HeightPixels(const int iFontIndex, const float scale) +{ + return syscall( UI_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( UI_R_FONT_DRAWSTRING, ox, oy, text, rgba, setIndex, iCharLimit, PASSFLOAT(scale)); +} + +qboolean trap_Language_IsAsian(void) +{ + return syscall( UI_LANGUAGE_ISASIAN ); +} + +qboolean trap_Language_UsesSpaces(void) +{ + return syscall( UI_LANGUAGE_USESSPACES ); +} + +unsigned int trap_AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation ) +{ + return syscall( UI_ANYLANGUAGE_READCHARFROMSTRING, psText, piAdvanceCount, pbIsTrailingPunctuation); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + char buf[1024]; + + if (name[0] == '*') { + trap_Cvar_VariableStringBuffer(name+1, buf, sizeof(buf)); + if (buf[0]) { + return syscall( UI_R_REGISTERSHADERNOMIP, &buf ); + } + } + return syscall( UI_R_REGISTERSHADERNOMIP, name ); +} + +//added so I don't have to store a string containing the path of +//the shader icon for a class -rww +void trap_R_ShaderNameFromIndex(char *name, int index) +{ + syscall( UI_R_SHADERNAMEFROMINDEX, name, index ); +} + +void trap_R_ClearScene( void ) { + syscall( UI_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( UI_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { + syscall( UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) { + syscall( UI_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( 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 ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( UI_R_MODELBOUNDS, model, mins, maxs ); +} + +void trap_UpdateScreen( void ) { + syscall( UI_UPDATESCREEN ); +} + +int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ) { + return syscall( UI_CM_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( UI_S_STARTLOCALSOUND, sfx, channelNum ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample ) { + return syscall( UI_S_REGISTERSOUND, sample ); +} + +void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { + syscall( UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen ); +} + +void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + syscall( UI_KEY_GETBINDINGBUF, keynum, buf, buflen ); +} + +void trap_Key_SetBinding( int keynum, const char *binding ) { + syscall( UI_KEY_SETBINDING, keynum, binding ); +} + +qboolean trap_Key_IsDown( int keynum ) { + return syscall( UI_KEY_ISDOWN, keynum ); +} + +qboolean trap_Key_GetOverstrikeMode( void ) { + return syscall( UI_KEY_GETOVERSTRIKEMODE ); +} + +void trap_Key_SetOverstrikeMode( qboolean state ) { + syscall( UI_KEY_SETOVERSTRIKEMODE, state ); +} + +void trap_Key_ClearStates( void ) { + syscall( UI_KEY_CLEARSTATES ); +} + +int trap_Key_GetCatcher( void ) { + return syscall( UI_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { + syscall( UI_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 trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( UI_GETGLCONFIG, glconfig ); +} + +int trap_GetConfigString( int index, char* buff, int buffsize ) { + return syscall( UI_GETCONFIGSTRING, index, buff, buffsize ); +} + +int trap_LAN_GetServerCount( int source ) { + return syscall( UI_LAN_GETSERVERCOUNT, source ); +} + +void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { + syscall( UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen ); +} + +void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { + syscall( UI_LAN_GETSERVERINFO, source, n, buf, buflen ); +} + +int trap_LAN_GetServerPing( int source, int n ) { + return syscall( UI_LAN_GETSERVERPING, source, n ); +} + +int trap_LAN_GetPingQueueCount( void ) { + return syscall( UI_LAN_GETPINGQUEUECOUNT ); +} + +int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ) { + return syscall( UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen ); +} + +void trap_LAN_SaveCachedServers() { + syscall( UI_LAN_SAVECACHEDSERVERS ); +} + +void trap_LAN_LoadCachedServers() { + syscall( UI_LAN_LOADCACHEDSERVERS ); +} + +void trap_LAN_ResetPings(int n) { + syscall( UI_LAN_RESETPINGS, n ); +} + +void trap_LAN_ClearPing( int n ) { + syscall( UI_LAN_CLEARPING, n ); +} + +void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { + syscall( UI_LAN_GETPING, n, buf, buflen, pingtime ); +} + +void trap_LAN_GetPingInfo( int n, char *buf, int buflen ) { + syscall( UI_LAN_GETPINGINFO, n, buf, buflen ); +} + +void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ) { + syscall( UI_LAN_MARKSERVERVISIBLE, source, n, visible ); +} + +int trap_LAN_ServerIsVisible( int source, int n) { + return syscall( UI_LAN_SERVERISVISIBLE, source, n ); +} + +qboolean trap_LAN_UpdateVisiblePings( int source ) { + return syscall( UI_LAN_UPDATEVISIBLEPINGS, source ); +} + +int trap_LAN_AddServer(int source, const char *name, const char *addr) { + return syscall( UI_LAN_ADDSERVER, source, name, addr ); +} + +void trap_LAN_RemoveServer(int source, const char *addr) { + syscall( UI_LAN_REMOVESERVER, source, addr ); +} + +int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { + return syscall( UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2 ); +} + +int trap_MemoryRemaining( void ) { + return syscall( UI_MEMORY_REMAINING ); +} + +#ifdef USE_CD_KEY + +void trap_GetCDKey( char *buf, int buflen ) { + syscall( UI_GET_CDKEY, buf, buflen ); +} + +void trap_SetCDKey( char *buf ) { + syscall( UI_SET_CDKEY, buf ); +} + +qboolean trap_VerifyCDKey( const char *key, const char *chksum) { + return syscall( UI_VERIFY_CDKEY, key, chksum); +} + +#endif // USE_CD_KEY + +int trap_PC_AddGlobalDefine( char *define ) { + return syscall( UI_PC_ADD_GLOBAL_DEFINE, define ); +} + +int trap_PC_LoadSource( const char *filename ) { + return syscall( UI_PC_LOAD_SOURCE, filename ); +} + +int trap_PC_FreeSource( int handle ) { + return syscall( UI_PC_FREE_SOURCE, handle ); +} + +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { + return syscall( UI_PC_READ_TOKEN, handle, pc_token ); +} + +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { + return syscall( UI_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +int trap_PC_LoadGlobalDefines ( const char* filename ) +{ + return syscall ( UI_PC_LOAD_GLOBAL_DEFINES, filename ); +} + +void trap_PC_RemoveAllGlobalDefines ( void ) +{ + syscall ( UI_PC_REMOVE_ALL_GLOBAL_DEFINES ); +} + +void trap_S_StopBackgroundTrack( void ) { + syscall( UI_S_STOPBACKGROUNDTRACK ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bReturnWithoutStarting) { + syscall( UI_S_STARTBACKGROUNDTRACK, intro, loop, bReturnWithoutStarting ); +} + +int trap_RealTime(qtime_t *qtime) { + return syscall( UI_REAL_TIME, qtime ); +} + +// 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(UI_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(UI_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(UI_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic (int handle) { + syscall(UI_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(UI_CIN_SETEXTENTS, handle, x, y, w, h); +} + + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { + syscall( UI_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +int trap_SP_GetNumLanguages( void ) +{ + return syscall( UI_SP_GETNUMLANGUAGES ); +} + +void trap_GetLanguageName( const int languageIndex, char *buffer ) +{ + syscall( UI_SP_GETLANGUAGENAME, languageIndex, buffer); +} + +int trap_SP_GetStringTextString(const char *text, char *buffer, int bufferLength) +{ + return syscall( UI_SP_GETSTRINGTEXTSTRING, text, buffer, bufferLength ); +} +/* +Ghoul2 Insert Start +*/ +void trap_G2_ListModelSurfaces(void *ghlInfo) +{ + syscall( UI_G2_LISTSURFACES, ghlInfo); +} + +void trap_G2_ListModelBones(void *ghlInfo, int frame) +{ + syscall( UI_G2_LISTBONES, ghlInfo, frame); +} + +void trap_G2_SetGhoul2ModelIndexes(void *ghoul2, qhandle_t *modelList, qhandle_t *skinList) +{ + syscall( UI_G2_SETMODELS, ghoul2, modelList, skinList); +} + +qboolean trap_G2_HaveWeGhoul2Models(void *ghoul2) +{ + return (qboolean)(syscall(UI_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(UI_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(UI_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(UI_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(UI_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(UI_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 ( UI_G2_COLLISIONDETECT, collRecMap, ghoul2, angles, position, frameNumber, entNum, rayStart, rayEnd, scale, traceFlags, useLod, PASSFLOAT(fRadius) ); +} + +void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr) +{ + syscall(UI_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(UI_G2_ANGLEOVERRIDE, ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, blendTime, currentTime)); +} + +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(UI_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(UI_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(UI_G2_GETBONEFRAME, ghoul2, boneName, currentTime, currentFrame, modelList, modelIndex); +} + +void trap_G2API_GetGLAName(void *ghoul2, int modelIndex, char *fillBuf) +{ + syscall(UI_G2_GETGLANAME, ghoul2, modelIndex, fillBuf); +} + +int trap_G2API_CopyGhoul2Instance(void *g2From, void *g2To, int modelIndex) +{ + return syscall(UI_G2_COPYGHOUL2INSTANCE, g2From, g2To, modelIndex); +} + +void trap_G2API_CopySpecificGhoul2Model(void *g2From, int modelFrom, void *g2To, int modelTo) +{ + syscall(UI_G2_COPYSPECIFICGHOUL2MODEL, g2From, modelFrom, g2To, modelTo); +} + +void trap_G2API_DuplicateGhoul2Instance(void *g2From, void **g2To) +{ + syscall(UI_G2_DUPLICATEGHOUL2INSTANCE, g2From, g2To); +} + +qboolean trap_G2API_HasGhoul2ModelOnIndex(void *ghlInfo, int modelIndex) +{ + return syscall(UI_G2_HASGHOUL2MODELONINDEX, ghlInfo, modelIndex); +} + +qboolean trap_G2API_RemoveGhoul2Model(void *ghlInfo, int modelIndex) +{ + return syscall(UI_G2_REMOVEGHOUL2MODEL, ghlInfo, modelIndex); +} + +int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName) +{ + return syscall(UI_G2_ADDBOLT, ghoul2, modelIndex, boneName); +} + +void trap_G2API_SetBoltInfo(void *ghoul2, int modelIndex, int boltInfo) +{ + syscall(UI_G2_SETBOLTON, ghoul2, modelIndex, boltInfo); +} + +qboolean trap_G2API_SetRootSurface(void *ghoul2, const int modelIndex, const char *surfaceName) +{ + return syscall(UI_G2_SETROOTSURFACE, ghoul2, modelIndex, surfaceName); +} + +qboolean trap_G2API_SetSurfaceOnOff(void *ghoul2, const char *surfaceName, const int flags) +{ + return syscall(UI_G2_SETSURFACEONOFF, ghoul2, surfaceName, flags); +} + +qboolean trap_G2API_SetNewOrigin(void *ghoul2, const int boltIndex) +{ + return syscall(UI_G2_SETNEWORIGIN, ghoul2, boltIndex); +} + +int trap_G2API_GetTime(void) +{ + return syscall(UI_G2_GETTIME); +} + +void trap_G2API_SetTime(int time, int clock) +{ + syscall(UI_G2_SETTIME, time, clock); +} + +//rww - RAGDOLL_BEGIN +void trap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params) +{ + syscall(UI_G2_SETRAGDOLL, ghoul2, params); +} + +void trap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params) +{ + syscall(UI_G2_ANIMATEG2MODELS, ghoul2, time, params); +} +//rww - RAGDOLL_END + +qboolean trap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params) +{ + return syscall(UI_G2_SETBONEIKSTATE, ghoul2, time, boneName, ikState, params); +} + +qboolean trap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params) +{ + return syscall(UI_G2_IKMOVE, ghoul2, time, params); +} + +void trap_G2API_GetSurfaceName(void *ghoul2, int surfNumber, int modelIndex, char *fillBuf) +{ + syscall(UI_G2_GETSURFACENAME, ghoul2, surfNumber, modelIndex, fillBuf); +} + +qboolean trap_G2API_AttachG2Model(void *ghoul2From, int modelIndexFrom, void *ghoul2To, int toBoltIndex, int toModel) +{ + return syscall(UI_G2_ATTACHG2MODEL, ghoul2From, modelIndexFrom, ghoul2To, toBoltIndex, toModel); +} +/* +Ghoul2 Insert End +*/ + +#include "../namespace_end.h" diff --git a/codemp/ui/ui_util.c b/codemp/ui/ui_util.c new file mode 100644 index 0000000..96d04e8 --- /dev/null +++ b/codemp/ui/ui_util.c @@ -0,0 +1,11 @@ +// ui_util.c +// +// origin: rad +// new ui support stuff +// +// memory, string alloc + +void RichIsGettingTiredOfEmptyFileVMCompilerWarnings() +{ + int andUnderstandablySo = 0; +} diff --git a/codemp/ui/vssver.scc b/codemp/ui/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..6a3ff90801b6f6cd527e5f22b4243780f66ff1de GIT binary patch literal 352 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiXze_g`-hLHgb-U4aCq ztiN&-*?{t2fc*J4R`PPsEd%TS3gl0S%RTs+yC#tnDE|$}e_W+=F*8~?ksrwa4&-w` zj(F8sYnr$f$o~Q4cU@!K``-F;0+7k@6G%M#Y!%h~^lpLxkpBzFkFGt&cD!;zLKKky z8_4&1xUot+e`$g!kpBnBmv|Cn`hV)01Rfy&FObjTx0bhe&sMN`|AB^t7(9yJzuhyD w4=C>`$iOhyLCDEBE)eYAH9`yw^@ZK(>4n{3{wAQIS1#%w-&*%Qfg7kE0Q^^LIsgCw literal 0 HcmV?d00001 diff --git a/codemp/unix/files_linux.cpp b/codemp/unix/files_linux.cpp new file mode 100644 index 0000000..9425d87 --- /dev/null +++ b/codemp/unix/files_linux.cpp @@ -0,0 +1,3071 @@ +/***************************************************************************** + * 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 "../qcommon/files.h" + +//#include //rww - included to make fs_copyfiles 2 related functions happy. +#include "../qcommon/platform.h" + +// productId: This file is copyright 2000 Raven Software, and may not be duplicated except during a licensed installation of the full commercial version of Star Wars: Jedi Outcast +static byte fs_scrambledProductId[165] = { + 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, 180, 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, 95, 164, 42, 36, 189, 94, 126 +}; + + +/* +================= +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; +} + +#ifdef NOTHERE +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; +} + +#endif +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(); + } + +#ifndef DEDICATED + // 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. + } + } +#endif + 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 ); + } + +#ifndef DEDICATED + // 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; + } + } +#endif + 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_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 ); + + // 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" ); + } + + 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/unix/ftol.nasm b/codemp/unix/ftol.nasm new file mode 100644 index 0000000..48437f3 --- /dev/null +++ b/codemp/unix/ftol.nasm @@ -0,0 +1,131 @@ +; +; qftol -- fast floating point to long conversion. +; + +segment .data + +temp dd 0.0 +fpucw dd 0 + +; Precision Control Field , 2 bits / 0x0300 +; PC24 0x0000 Single precision (24 bits). +; PC53 0x0200 Double precision (53 bits). +; PC64 0x0300 Extended precision (64 bits). + +; Rounding Control Field, 2 bits / 0x0C00 +; RCN 0x0000 Rounding to nearest (even). +; RCD 0x0400 Rounding down (directed, minus). +; RCU 0x0800 Rounding up (directed plus). +; RC0 0x0C00 Rounding towards zero (chop mode). + + +; rounding towards nearest (even) +cw027F dd 0x027F ; double precision +cw037F dd 0x037F ; extended precision + +; rounding towards zero (chop mode) +cw0E7F dd 0x0E7F ; double precision +cw0F7F dd 0x0F7F ; extended precision + + +segment .text + +; +; int qftol( void ) - default control word +; + +global qftol + +qftol: + fistp dword [temp] + mov eax, [temp] + ret + + +; +; int qftol027F( void ) - DirectX FPU +; + +global qftol027F + +qftol027F: + fnstcw [fpucw] + fldcw [cw027F] + fistp dword [temp] + fldcw [fpucw] + mov eax, [temp] + ret + +; +; int qftol037F( void ) - Linux FPU +; + +global qftol037F + +qftol037F: + fnstcw [fpucw] + fldcw [cw037F] + fistp dword [temp] + fldcw [fpucw] + mov eax, [temp] + ret + + +; +; int qftol0F7F( void ) - ANSI +; + +global qftol0F7F + +qftol0F7F: + fnstcw [fpucw] + fldcw [cw0F7F] + fistp dword [temp] + fldcw [fpucw] + mov eax, [temp] + ret + +; +; int qftol0E7F( void ) +; + +global qftol0E7F + +qftol0E7F: + fnstcw [fpucw] + fldcw [cw0E7F] + fistp dword [temp] + fldcw [fpucw] + mov eax, [temp] + ret + + + +; +; long Q_ftol( float q ) +; + +global Q_ftol + +Q_ftol: + fld dword [esp+4] + fistp dword [temp] + mov eax, [temp] + ret + + +; +; long qftol0F7F( float q ) - Linux FPU +; + +global Q_ftol0F7F + +Q_ftol0F7F: + fnstcw [fpucw] + fld dword [esp+4] + fldcw [cw0F7F] + fistp dword [temp] + fldcw [fpucw] + mov eax, [temp] + ret + diff --git a/codemp/unix/linux_common.c b/codemp/unix/linux_common.c new file mode 100644 index 0000000..042e3b1 --- /dev/null +++ b/codemp/unix/linux_common.c @@ -0,0 +1,323 @@ +/** + * GAS syntax equivalents of the MSVC asm memory calls in common.c + * + * The following changes have been made to the asm: + * 1. Registers are loaded by the inline asm arguments when possible + * 2. Labels have been changed to local label format (0,1,etc.) to allow inlining + * + * HISTORY: + * AH - Created on 08 Dec 2000 + */ + +#include // AH - for size_t +#include + +// bk001207 - we need something under Linux, too. Mac? +#if 1 // defined(C_ONLY) // bk010102 - dedicated? +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); + +void _copyDWord (unsigned int* dest, const unsigned int constant, const unsigned int count) { + // MMX version not used on standard Pentium MMX + // because the dword version is faster (with + // proper destination prefetching) + __asm__ __volatile__ (" + //mov eax,constant // eax = val + //mov edx,dest // dest + //mov ecx,count + movd %%eax, %%mm0 + punpckldq %%mm0, %%mm0 + + // ensure that destination is qword aligned + + testl $7, %%edx // qword padding? + jz 0f + movl %%eax, (%%edx) + decl %%ecx + addl $4, %%edx + +0: movl %%ecx, %%ebx + andl $0xfffffff0, %%ecx + jz 2f + jmp 1f + .align 16 + + // funny ordering here to avoid commands + // that cross 32-byte boundaries (the + // [edx+0] version has a special 3-byte opcode... +1: movq %%mm0, 8(%%edx) + movq %%mm0, 16(%%edx) + movq %%mm0, 24(%%edx) + movq %%mm0, 32(%%edx) + movq %%mm0, 40(%%edx) + movq %%mm0, 48(%%edx) + movq %%mm0, 56(%%edx) + movq %%mm0, (%%edx) + addl $64, %%edx + subl $16, %%ecx + jnz 1b +2: + movl %%ebx, %%ecx // ebx = cnt + andl $0xfffffff0, %%ecx // ecx = cnt&~15 + subl %%ecx, %%ebx + jz 6f + cmpl $8, %%ebx + jl 3f + + movq %%mm0, (%%edx) + movq %%mm0, 8(%%edx) + movq %%mm0, 16(%%edx) + movq %%mm0, 24(%%edx) + addl $32, %%edx + subl $8, %%ebx + jz 6f + +3: cmpl $4, %%ebx + jl 4f + + movq %%mm0, (%%edx) + movq %%mm0, 8(%%edx) + addl $16, %%edx + subl $4, %%ebx + +4: cmpl $2, %%ebx + jl 5f + movq %%mm0, (%%edx) + addl $8, %%edx + subl $2, %%ebx + +5: cmpl $1, %%ebx + jl 6f + movl %%eax, (%%edx) +6: + emms + " + : : "a" (constant), "c" (count), "d" (dest) + : "%ebx", "%edi", "%esi", "cc", "memory"); +} + +// 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__ __volatile__ (" + pushl %%edi + pushl %%esi + //mov ecx,count + cmpl $0, %%ecx // count = 0 check (just to be on the safe side) + je 6f + //mov edx,dest + movl %0, %%ebx + cmpl $32, %%ecx // padding only? + jl 1f + + movl %%ecx, %%edi + andl $0xfffffe00, %%edi // edi = count&~31 + subl $32, %%edi + + .align 16 +0: + movl (%%ebx, %%edi, 1), %%eax + movl 4(%%ebx, %%edi, 1), %%esi + movl %%eax, (%%edx, %%edi, 1) + movl %%esi, 4(%%edx, %%edi, 1) + movl 8(%%ebx, %%edi, 1), %%eax + movl 12(%%ebx, %%edi, 1), %%esi + movl %%eax, 8(%%edx, %%edi, 1) + movl %%esi, 12(%%edx, %%edi, 1) + movl 16(%%ebx, %%edi, 1), %%eax + movl 20(%%ebx, %%edi, 1), %%esi + movl %%eax, 16(%%edx, %%edi, 1) + movl %%esi, 20(%%edx, %%edi, 1) + movl 24(%%ebx, %%edi, 1), %%eax + movl 28(%%ebx, %%edi, 1), %%esi + movl %%eax, 24(%%edx, %%edi, 1) + movl %%esi, 28(%%edx, %%edi, 1) + subl $32, %%edi + jge 0b + + movl %%ecx, %%edi + andl $0xfffffe00, %%edi + addl %%edi, %%ebx // increase src pointer + addl %%edi, %%edx // increase dst pointer + andl $31, %%ecx // new count + jz 6f // if count = 0, get outta here + +1: + cmpl $16, %%ecx + jl 2f + movl (%%ebx), %%eax + movl %%eax, (%%edx) + movl 4(%%ebx), %%eax + movl %%eax, 4(%%edx) + movl 8(%%ebx), %%eax + movl %%eax, 8(%%edx) + movl 12(%%ebx), %%eax + movl %%eax, 12(%%edx) + subl $16, %%ecx + addl $16, %%ebx + addl $16, %%edx +2: + cmpl $8, %%ecx + jl 3f + movl (%%ebx), %%eax + movl %%eax, (%%edx) + movl 4(%%ebx), %%eax + subl $8, %%ecx + movl %%eax, 4(%%edx) + addl $8, %%ebx + addl $8, %%edx +3: + cmpl $4, %%ecx + jl 4f + movl (%%ebx), %%eax // here 4-7 bytes + addl $4, %%ebx + subl $4, %%ecx + movl %%eax, (%%edx) + addl $4, %%edx +4: // 0-3 remaining bytes + cmpl $2, %%ecx + jl 5f + movw (%%ebx), %%ax // two bytes + cmpl $3, %%ecx // less than 3? + movw %%ax, (%%edx) + jl 6f + movb 2(%%ebx), %%al // last byte + movb %%al, 2(%%edx) + jmp 6f +5: + cmpl $1, %%ecx + jl 6f + movb (%%ebx), %%al + movb %%al, (%%edx) +6: + popl %%esi + popl %%edi + " + : : "m" (src), "d" (dest), "c" (count) + : "%eax", "%ebx", "%edi", "%esi", "cc", "memory"); +} + +void Com_Memset (void* dest, const int val, const size_t count) +{ + unsigned int fillval; + + if (count < 8) + { + __asm__ __volatile__ (" + //mov edx,dest + //mov eax, val + movb %%al, %%ah + movl %%eax, %%ebx + andl $0xffff, %%ebx + shll $16, %%eax + addl %%ebx, %%eax // eax now contains pattern + //mov ecx,count + cmpl $4, %%ecx + jl 0f + movl %%eax, (%%edx) // copy first dword + addl $4, %%edx + subl $4, %%ecx + 0: cmpl $2, %%ecx + jl 1f + movw %%ax, (%%edx) // copy 2 bytes + addl $2, %%edx + subl $2, %%ecx + 1: cmpl $0, %%ecx + je 2f + movb %%al, (%%edx) // copy single byte + 2: + " + : : "d" (dest), "a" (val), "c" (count) + : "%ebx", "%edi", "%esi", "cc", "memory"); + + 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__ __volatile__ (" // padding of 0-3 bytes + //mov ecx,count + movl %%ecx, %%eax + andl $3, %%ecx + jz 1f + andl $0xffffff00, %%eax + //mov ebx,dest + addl %%eax, %%edx + movl %0, %%eax + cmpl $2, %%ecx + jl 0f + movw %%ax, (%%edx) + cmpl $2, %%ecx + je 1f + movb %%al, 2(%%edx) + jmp 1f +0: + cmpl $0, %%ecx + je 1f + movb %%al, (%%edx) +1: + " + : : "m" (fillval), "c" (count), "d" (dest) + : "%eax", "%ebx", "%edi", "%esi", "cc", "memory"); +} + +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__ __volatile__ (" + //mov ebx,s + //mov ecx,bytes + cmpl $4096, %%ecx // clamp to 4kB + jle 0f + movl $4096, %%ecx + 0: + addl $0x1f, %%ecx + shrl $5, %%ecx // number of cache lines + jz 2f + jmp 1f + + .align 16 + 1: testb %%al, (%%edx) + addl $32, %%edx + decl %%ecx + jnz 1b + 2: + " + : : "d" (s), "c" (bytes) + : "%eax", "%ebx", "%edi", "%esi", "memory", "cc"); + + break; + } +} + +#endif diff --git a/codemp/unix/linux_glimp.c b/codemp/unix/linux_glimp.c new file mode 100644 index 0000000..4de5929 --- /dev/null +++ b/codemp/unix/linux_glimp.c @@ -0,0 +1,1543 @@ +/* +** 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 +#ifdef __linux__ +#include +#include +#endif +#include +#include +#include +#include +#include + +// bk001204 +#include + +// bk001206 - from my Heretic2 by way of Ryan's Fakk2 +// Needed for the new X11_PendingInput() function. +#include +#include +#include + +#include "../renderer/tr_local.h" +#include "../client/client.h" +#include "linux_local.h" // bk001130 + +#include "unix_glw.h" + +#include + +#include +#include + +#include +#include + +#define WINDOW_CLASS_NAME "Quake 3: Arena" + +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; + +// bk001206 - not needed anymore +// 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 mwx, mwy; +static int mx = 0, my = 0; + +// Time mouse was reset, we ignore the first 50ms of the mouse to allow settling of events +static int mouseResetTime = 0; +#define MOUSE_RESET_DELAY 50 + +static cvar_t *in_mouse; +static cvar_t *in_dgamouse; + +// bk001130 - from cvs1.17 (mkv), but not static +cvar_t *in_joystick = NULL; +cvar_t *in_joystickDebug = NULL; +cvar_t *joy_threshold = NULL; + +cvar_t *r_allowSoftwareGL; // don't abort out if the pixelformat claims software +cvar_t *r_previousglDriver; + +qboolean dgamouse = qfalse; +qboolean vidmode_ext = qfalse; + +static int win_x, win_y; + +static XF86VidModeModeInfo **vidmodes; +//static int default_dotclock_vidmode; // bk001204 - unused +static int num_vidmodes; +static qboolean vidmode_active = qfalse; + +static int mouse_accel_numerator; +static int mouse_accel_denominator; +static int mouse_threshold; + +/* +* Find the first occurrence of find in s. +*/ +// bk001130 - from cvs1.17 (mkv), const +// bk001130 - made first argument const +static const char *Q_stristr( const char *s, const char *find) +{ +register char c, sc; +register size_t len; + + if ((c = *find++) != 0) { + if (c >= 'a' && c <= 'z') { + c -= ('a' - 'A'); + } + len = strlen(find); + do { + do { + if ((sc = *s++) == 0) + return NULL; + if (sc >= 'a' && sc <= 'z') { + sc -= ('a' - 'A'); + } + } while (sc != c); + } while (Q_stricmpn(s, find, len) != 0); + s--; + } + return s; +} + +/*****************************************************************************/ +/* KEYBOARD */ +/*****************************************************************************/ + +// bk001204 - unused +// 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; // bk001204 - unused + // int i; // bk001204 - unused + + *key = 0; + + XLookupString(ev, buf, sizeof buf, &keysym, 0); + + 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; + + // bk001206 - from Ryan's Fakk2 + //case XK_BackSpace: *key = 8; break; // ctrl-h + case XK_BackSpace: *key = K_BACKSPACE; 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; + + // bk001130 - from cvs1.17 (mkv) + case XK_exclam: *key = '1'; break; + case XK_at: *key = '2'; break; + case XK_numbersign: *key = '3'; break; + case XK_dollar: *key = '4'; break; + case XK_percent: *key = '5'; break; + case XK_asciicircum: *key = '6'; break; + case XK_ampersand: *key = '7'; break; + case XK_asterisk: *key = '8'; break; + case XK_parenleft: *key = '9'; break; + case XK_parenright: *key = '0'; 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 + XWarpPointer(dpy, None, win, + 0, 0, 0, 0, + glConfig.vidWidth / 2, glConfig.vidHeight / 2); + XSync(dpy, False); + + XDefineCursor(dpy, win, CreateNullCursor(dpy, win)); + + XGrabPointer(dpy, win, // bk010108 - do this earlier? + False, + MOUSE_MASK, + GrabModeAsync, GrabModeAsync, + win, + None, + CurrentTime); + + XGetPointerControl(dpy, &mouse_accel_numerator, &mouse_accel_denominator, + &mouse_threshold); + + XChangePointerControl(dpy, True, True, 1, 1, 0); + + XSync(dpy, False); + + mouseResetTime = Sys_Milliseconds (); + + 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 { + mwx = glConfig.vidWidth / 2; + mwy = glConfig.vidHeight / 2; + mx = my = 0; + } + + XGrabKeyboard(dpy, win, + False, + GrabModeAsync, GrabModeAsync, + CurrentTime); + + XSync(dpy, False); +} + +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); + + XWarpPointer(dpy, None, win, + 0, 0, 0, 0, + glConfig.vidWidth / 2, glConfig.vidHeight / 2); + + // inviso cursor + XUndefineCursor(dpy, win); +} + + + +// bk001206 - from Ryan's Fakk2 +/** + * XPending() actually performs a blocking read + * if no events available. From Fakk2, by way of + * Heretic2, by way of SDL, original idea GGI project. + * The benefit of this approach over the quite + * badly behaved XAutoRepeatOn/Off is that you get + * focus handling for free, which is a major win + * with debug and windowed mode. It rests on the + * assumption that the X server will use the + * same timestamp on press/release event pairs + * for key repeats. + */ +static qboolean X11_PendingInput(void) { + + assert(dpy != NULL); + + // Flush the display connection + // and look to see if events are queued + XFlush( dpy ); + if ( XEventsQueued( dpy, QueuedAlready) ) { + return qtrue; + } + + // More drastic measures are required -- see if X is ready to talk + { + static struct timeval zero_time; + int x11_fd; + fd_set fdset; + + x11_fd = ConnectionNumber( dpy ); + FD_ZERO(&fdset); + FD_SET(x11_fd, &fdset); + if ( select(x11_fd+1, &fdset, NULL, NULL, &zero_time) == 1 ) { + return(XPending(dpy)); + } + } + + // Oh well, nothing is ready .. + return qfalse; +} + + +// bk001206 - from Ryan's Fakk2. See above. +static qboolean repeated_press(XEvent *event) +{ + XEvent peekevent; + qboolean repeated = qfalse; + + assert(dpy != NULL); + + if (X11_PendingInput()) + { + XPeekEvent(dpy, &peekevent); + + if ((peekevent.type == KeyPress) && + (peekevent.xkey.keycode == event->xkey.keycode) && + (peekevent.xkey.time == event->xkey.time)) + { + repeated = qtrue; + XNextEvent(dpy, &peekevent); // skip event. + } // if + } // if + + return(repeated); +} // repeated_press + + + +static void HandleEvents(void) +{ + int b; + int key; + XEvent event; + qboolean dowarp = qfalse; + char *p; + int dx, dy; + int t; + + 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: + + // bk001206 - handle key repeat w/o XAutRepatOn/Off + // also: not done if console/menu is active. + // From Ryan's Fakk2. + // see game/q_shared.h, KEYCATCH_* . 0 == in 3d game. + if (cls.keyCatchers == 0) { // FIXME: KEYCATCH_NONE + if (repeated_press(&event) == qtrue) + continue; + } // if + XLateKey(&event.xkey, &key); + + Sys_QueEvent( 0, SE_KEY, key, qfalse, 0, NULL ); + 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; + t = Sys_Milliseconds(); + if (t - mouseResetTime > MOUSE_RESET_DELAY ) { + Sys_QueEvent( t, SE_MOUSE, mx, my, 0, NULL ); + } + mx = my = 0; + } + else + { +// ri.Printf( PRINT_ALL, "MotionNotify: %d,%d: ", event.xmotion.x, event.xmotion.y ); + // If it's a center motion, we've just returned from our warp + if (event.xmotion.x == glConfig.vidWidth/2 && + event.xmotion.y == glConfig.vidHeight/2) { + mwx = glConfig.vidWidth/2; + mwy = glConfig.vidHeight/2; +// ri.Printf( PRINT_ALL, "SE_MOUSE (%d,%d)\n", mx, my ); + t = Sys_Milliseconds(); + if (t - mouseResetTime > MOUSE_RESET_DELAY ) { + Sys_QueEvent( t, SE_MOUSE, mx, my, 0, NULL ); + } + mx = my = 0; + break; + } + + dx = ((int)event.xmotion.x - mwx); + dy = ((int)event.xmotion.y - mwy); + if (abs(dx) > 1) + mx += dx * 2; + else + mx += dx; + if (abs(dy) > 1) + my += dy * 2; + else + my += dy; + +// ri.Printf( PRINT_ALL, "mx=%d,my=%d [%d - %d,%d - %d]\n", mx, my, event.xmotion.x, mwx, event.xmotion.y, mwy ); + mwx = event.xmotion.x; + mwy = event.xmotion.y; + dowarp = qtrue; + } + } + break; + + case ButtonPress: + if (event.xbutton.button == 4) { + Sys_QueEvent( 0, SE_KEY, K_MWHEELUP, qtrue, 0, NULL ); + } else if (event.xbutton.button == 5) { + Sys_QueEvent( 0, SE_KEY, K_MWHEELDOWN, qtrue, 0, NULL ); + } else { + 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: + if (event.xbutton.button == 4) { + Sys_QueEvent( 0, SE_KEY, K_MWHEELUP, qfalse, 0, NULL ); + } else if (event.xbutton.button == 5) { + Sys_QueEvent( 0, SE_KEY, K_MWHEELDOWN, qfalse, 0, NULL ); + } else { + 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) { + 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) { + 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;; + +void Sys_Exit(int); // bk010104 - abstraction + +static void signal_handler(int sig) // bk010104 - replace this... +{ + if (signalcaught) { + printf("DOUBLE SIGNAL FAULT: Received signal %d, exiting...\n", sig); + Sys_Exit(1); // bk010104 - abstraction + } + + signalcaught = qtrue; + printf("Received signal %d, exiting...\n", sig); + GLimp_Shutdown(); // bk010104 - shouldn't this be CL_Shutdown + Sys_Exit(1); // bk010104 - abstraction +} + +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(); + // bk001206 - replaced with H2/Fakk2 solution + // XAutoRepeatOn(dpy); + // autorepeaton = qfalse; // bk001130 - from cvs1.17 (mkv) + 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 +*/ +// bk001204 - prototype needed +int GLW_SetMode( const char *drivername, int mode, qboolean fullscreen ); +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 ( Q_stristr( 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; + const char* glstring; // bk001130 - from cvs1.17 (mkv) + + 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; + } + + // 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); + } + } + + 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) { + 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); + + ri.Printf(PRINT_ALL, "XFree86-VidModeExtension Activated at %dx%d\n", + actualWidth, actualHeight); + + } else { + fullscreen = 0; + ri.Printf(PRINT_ALL, "XFree86-VidModeExtension: No acceptable modes found\n"); + } + } else { + ri.Printf(PRINT_ALL, "XFree86-VidModeExtension: Ignored on non-fullscreen/Voodoo\n"); + } + } + + + 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; + + visinfo = qglXChooseVisual(dpy, scrnum, attrib); + if (!visinfo) { + continue; + } + + 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); + + XStoreName( dpy, win, WINDOW_CLASS_NAME ); + + XMapWindow( dpy, win ); + + if (vidmode_active) + XMoveWindow(dpy, win, 0, 0); + + XFlush(dpy); + XSync(dpy,False); // bk001130 - from cvs1.17 (mkv) + ctx = qglXCreateContext(dpy, visinfo, NULL, True); + XSync(dpy,False); // bk001130 - from cvs1.17 (mkv) + + qglXMakeCurrent(dpy, win, ctx); + + // bk001130 - from cvs1.17 (mkv) + glstring = qglGetString (GL_RENDERER); + ri.Printf( PRINT_ALL, "GL_RENDERER: %s\n", glstring ); + + // bk010122 - new software token (Indirect) + if ( !Q_stricmp( glstring, "Mesa X11") + || !Q_stricmp( glstring, "Mesa GLX Indirect") ) + { + if ( !r_allowSoftwareGL->integer ) { + ri.Printf( PRINT_ALL, "\n\n***********************************************************\n" ); + ri.Printf( PRINT_ALL, " You are using software Mesa (no hardware acceleration)! \n" ); + ri.Printf( PRINT_ALL, " Driver DLL used: %s\n", drivername ); + ri.Printf( PRINT_ALL, " If this is intentional, add\n" ); + ri.Printf( PRINT_ALL, " \"+set r_allowSoftwareGL 1\"\n" ); + ri.Printf( PRINT_ALL, " to the command line when starting the game.\n" ); + ri.Printf( PRINT_ALL, "***********************************************************\n"); + GLimp_Shutdown( ); + return RSERR_INVALID_MODE; + } else { + ri.Printf( PRINT_ALL, "...using software Mesa (r_allowSoftwareGL==1).\n" ); + } + } + + return RSERR_OK; +} + +/* +** GLW_InitExtensions +*/ +static void GLW_InitExtensions( void ) +{ + if ( !r_allowExtensions->integer ) + { + ri.Printf( PRINT_ALL, "*** IGNORING OPENGL EXTENSIONS ***\n" ); + return; + } + + ri.Printf( PRINT_ALL, "Initializing OpenGL extensions\n" ); + + // GL_S3_s3tc + if ( Q_stristr( 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, "...GL_S3_s3tc not found\n" ); + } + + // GL_EXT_texture_env_add + glConfig.textureEnvAddAvailable = qfalse; + if ( Q_stristr( 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_ARB_multitexture + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + if ( Q_stristr( 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 ) + { + qglGetIntegerv( GL_MAX_ACTIVE_TEXTURES_ARB, &glConfig.maxActiveTextures ); + + if ( glConfig.maxActiveTextures > 1 ) + { + ri.Printf( PRINT_ALL, "...using GL_ARB_multitexture\n" ); + } + else + { + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + ri.Printf( PRINT_ALL, "...not using GL_ARB_multitexture, < 2 texture units\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 + if ( Q_stristr( 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; // bk001204 - unused + + r_allowSoftwareGL = ri.Cvar_Get( "r_allowSoftwareGL", "0", CVAR_LATCH ); + + r_previousglDriver = ri.Cvar_Get( "r_previousglDriver", "", CVAR_ROM ); + + glConfig.deviceSupportsGamma = qfalse; + + InitSig(); + + // Hack here so that if the UI + if ( *r_previousglDriver->string ) { + // The UI changed it on us, hack it back + // This means the renderer can't be changed on the fly + ri.Cvar_Set( "r_glDriver", r_previousglDriver->string ); + } + + // + // 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" ); + + } + + // Save it in case the UI stomps it + ri.Cvar_Set( "r_previousglDriver", r_glDriver->string ); + + // This values force the UI to disable driver selection + glConfig.driverType = GLDRV_ICD; + glConfig.hardwareType = GLHW_GENERIC; + + // 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 ); + + // + // 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. + // + if ( Q_stricmp( lastValidRenderer->string, glConfig.renderer_string ) ) + { + glConfig.hardwareType = GLHW_GENERIC; + + ri.Cvar_Set( "r_textureMode", "GL_LINEAR_MIPMAP_NEAREST" ); + + // VOODOO GRAPHICS w/ 2MB + if ( Q_stristr( buf, "voodoo graphics/1 tmu/2 mb" ) ) + { + ri.Cvar_Set( "r_picmip", "2" ); + ri.Cvar_Get( "r_picmip", "1", CVAR_ARCHIVE | CVAR_LATCH ); + } + else + { + ri.Cvar_Set( "r_picmip", "1" ); + + if ( Q_stristr( buf, "rage 128" ) || Q_stristr( buf, "rage128" ) ) + { + ri.Cvar_Set( "r_finish", "0" ); + } + // Savage3D and Savage4 should always have trilinear enabled + else if ( Q_stristr( buf, "savage3d" ) || Q_stristr( buf, "s3 savage4" ) ) + { + ri.Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" ); + } + } + } + + // + // this is where hardware specific workarounds that should be + // detected/initialized every startup should go. + // + if ( Q_stristr( buf, "banshee" ) || Q_stristr( buf, "Voodoo_Graphics" ) ) + { + glConfig.hardwareType = GLHW_3DFX_2D3D; + } + else if ( Q_stristr( buf, "rage pro" ) || Q_stristr( buf, "RagePro" ) ) + { + glConfig.hardwareType = GLHW_RAGEPRO; + } + else if ( Q_stristr( buf, "permedia2" ) ) + { + glConfig.hardwareType = GLHW_PERMEDIA2; + } + else if ( Q_stristr( buf, "riva 128" ) ) + { + glConfig.hardwareType = GLHW_RIVA128; + } + else if ( Q_stristr( buf, "riva tnt " ) ) + { + } + + 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) +{ + // don't flip if drawing to front buffer + if ( stricmp( r_drawBuffer->string, "GL_FRONT" ) != 0 ) + { + qglXSwapBuffers(dpy, win); + } + + // check logging + QGL_EnableLogging( (qboolean)r_logFile->integer ); // bk001205 - was ->value +} + +#ifdef SMP +/* +=========================================================== + +SMP acceleration + +=========================================================== +*/ + +sem_t renderCommandsEvent; +sem_t renderCompletedEvent; +sem_t renderActiveEvent; + +void (*glimpRenderThread)( void ); + +void *GLimp_RenderThreadWrapper( void *stub ) { + glimpRenderThread(); + return NULL; +} + + +/* +======================= +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; // bk001204 - unused + +void *GLimp_RendererSleep( void ) { + void *data; + + // after this, the front end can exit GLimp_FrontEndSleep + sem_post ( &renderCompletedEvent ); + + sem_wait ( &renderCommandsEvent ); + + data = smpData; + + // after this, the main thread can exit GLimp_WakeRenderer + sem_post ( &renderActiveEvent ); + + return data; +} + + +void GLimp_FrontEndSleep( void ) { + sem_wait ( &renderCompletedEvent ); +} + + +void GLimp_WakeRenderer( void *data ) { + smpData = data; + + // after this, the renderer can continue through GLimp_RendererSleep + sem_post( &renderCommandsEvent ); + + sem_wait( &renderActiveEvent ); +} + +#else + +void GLimp_RenderThreadWrapper( void *stub ) { } +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) { + return qfalse; +} +void *GLimp_RendererSleep( void ) { + return NULL; +} +void GLimp_FrontEndSleep( void ) { } +void GLimp_WakeRenderer( void *data ) { } + +#endif + +/*****************************************************************************/ +/* 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); + + // bk001130 - from cvs.17 (mkv), joystick variables + in_joystick = Cvar_Get ("in_joystick", "0", CVAR_ARCHIVE|CVAR_LATCH); + // bk001130 - changed this to match win32 + in_joystickDebug = Cvar_Get ("in_debugjoystick", "0", CVAR_TEMP); + joy_threshold = Cvar_Get ("joy_threshold", "0.15", CVAR_ARCHIVE); // FIXME: in_joythreshold + + if (in_mouse->value) + mouse_avail = qtrue; + else + mouse_avail = qfalse; + + IN_StartupJoystick( ); // bk001130 - from cvs1.17 (mkv) +} + +void IN_Shutdown(void) +{ + mouse_avail = qfalse; +} + +void IN_Frame (void) { + + // bk001130 - from cvs 1.17 (mkv) + IN_JoyMove(); // FIXME: disable if on desktop? + + if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { + // 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; + } + // bk001206 - not used, now done the H2/Fakk2 way + //if (dpy && !autorepeaton) { + // XAutoRepeatOn(dpy); + // autorepeaton = qtrue; + //} + } + //else if (dpy && autorepeaton) { + //XAutoRepeatOff(dpy); + //autorepeaton = qfalse; + //} + + IN_ActivateMouse(); +} + +void IN_Activate(void) +{ +} + +// bk001130 - cvs1.17 joystick code (mkv) was here, no linux_joystick.c + +void Sys_SendKeyEvents (void) { + // XEvent event; // bk001204 - unused + + if (!dpy) + return; + HandleEvents(); +} + + +// bk010216 - added stubs for non-Linux UNIXes here +// FIXME - use NO_JOYSTICK or something else generic + +#if defined( __FreeBSD__ ) // rb010123 +void IN_StartupJoystick( void ) {} +void IN_JoyMove( void ) {} +#endif diff --git a/codemp/unix/linux_joystick.c b/codemp/unix/linux_joystick.c new file mode 100644 index 0000000..662c8b2 --- /dev/null +++ b/codemp/unix/linux_joystick.c @@ -0,0 +1,186 @@ +/* +** linux_joystick.c +** +** This file contains ALL Linux specific stuff having to do with the +** Joystick input. When a port is being made the following functions +** must be implemented by the port: +** +** Authors: mkv, bk +** +*/ + +#include +#include +#include +#include +#include // bk001204 + + +#include "../client/client.h" +#include "linux_local.h" + +/* We translate axes movement into keypresses. */ +int joy_keys[16] = { + K_LEFTARROW, K_RIGHTARROW, + K_UPARROW, K_DOWNARROW, + K_JOY16, K_JOY17, + K_JOY18, K_JOY19, + K_JOY20, K_JOY21, + K_JOY22, K_JOY23, + + K_JOY24, K_JOY25, + K_JOY26, K_JOY27 +}; + +/* Our file descriptor for the joystick device. */ +static int joy_fd = -1; + + +// bk001130 - from linux_glimp.c +extern cvar_t * in_joystick; +extern cvar_t * in_joystickDebug; +extern cvar_t * joy_threshold; + + +/**********************************************/ +/* Joystick routines. */ +/**********************************************/ +// bk001130 - from cvs1.17 (mkv), removed from linux_glimp.c +void IN_StartupJoystick( void ) +{ + int i = 0; + + joy_fd = -1; + + if( !in_joystick->integer ) { + Com_Printf( "Joystick is not active.\n" ); + return; + } + + for( i = 0; i < 4; i++ ) { + char filename[PATH_MAX]; + + snprintf( filename, PATH_MAX, "/dev/js%d", i ); + + joy_fd = open( filename, O_RDONLY | O_NONBLOCK ); + + if( joy_fd != -1 ) { + struct js_event event; + char axes = 0; + char buttons = 0; + char name[128]; + int n = -1; + + Com_Printf( "Joystick %s found\n", filename ); + + /* Get rid of initialization messages. */ + do { + n = read( joy_fd, &event, sizeof( event ) ); + + if( n == -1 ) { + break; + } + + } while( ( event.type & JS_EVENT_INIT ) ); + + /* Get joystick statistics. */ + ioctl( joy_fd, JSIOCGAXES, &axes ); + ioctl( joy_fd, JSIOCGBUTTONS, &buttons ); + + if( ioctl( joy_fd, JSIOCGNAME( sizeof( name ) ), name ) < 0 ) { + strncpy( name, "Unknown", sizeof( name ) ); + } + + Com_Printf( "Name: %s\n", name ); + Com_Printf( "Axes: %d\n", axes ); + Com_Printf( "Buttons: %d\n", buttons ); + + /* Our work here is done. */ + return; + } + + } + + /* No soup for you. */ + if( joy_fd == -1 ) { + Com_Printf( "No joystick found.\n" ); + return; + } + +} + +void IN_JoyMove( void ) +{ + /* Store instantaneous joystick state. Hack to get around + * event model used in Linux joystick driver. + */ + static int axes_state[16]; + /* Old bits for Quake-style input compares. */ + static unsigned int old_axes = 0; + /* Our current goodies. */ + unsigned int axes = 0; + int i = 0; + + if( joy_fd == -1 ) { + return; + } + + /* Empty the queue, dispatching button presses immediately + * and updating the instantaneous state for the axes. + */ + do { + int n = -1; + struct js_event event; + + n = read( joy_fd, &event, sizeof( event ) ); + + if( n == -1 ) { + /* No error, we're non-blocking. */ + break; + } + + if( event.type & JS_EVENT_BUTTON ) { + Sys_QueEvent( 0, SE_KEY, K_JOY1 + event.number, event.value, 0, NULL ); + } else if( event.type & JS_EVENT_AXIS ) { + + if( event.number >= 16 ) { + continue; + } + + axes_state[event.number] = event.value; + } else { + Com_Printf( "Unknown joystick event type\n" ); + } + + } while( 1 ); + + + /* Translate our instantaneous state to bits. */ + for( i = 0; i < 16; i++ ) { + float f = ( (float) axes_state[i] ) / 32767.0f; + + if( f < -joy_threshold->value ) { + axes |= ( 1 << ( i * 2 ) ); + } else if( f > joy_threshold->value ) { + axes |= ( 1 << ( ( i * 2 ) + 1 ) ); + } + + } + + /* Time to update axes state based on old vs. new. */ + for( i = 0; i < 16; i++ ) { + + if( ( axes & ( 1 << i ) ) && !( old_axes & ( 1 << i ) ) ) { + Sys_QueEvent( 0, SE_KEY, joy_keys[i], qtrue, 0, NULL ); + } + + if( !( axes & ( 1 << i ) ) && ( old_axes & ( 1 << i ) ) ) { + Sys_QueEvent( 0, SE_KEY, joy_keys[i], qfalse, 0, NULL ); + } + } + + /* Save for future generations. */ + old_axes = axes; +} + + diff --git a/codemp/unix/linux_local.h b/codemp/unix/linux_local.h new file mode 100644 index 0000000..008bea9 --- /dev/null +++ b/codemp/unix/linux_local.h @@ -0,0 +1,29 @@ +// linux_local.h: Linux-specific Quake3 header file + +void Sys_QueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ); +qboolean Sys_GetPacket ( netadr_t *net_from, msg_t *net_message ); +void Sys_SendKeyEvents (void); + +// Input subsystem + +void IN_Init (void); +void IN_Frame (void); +void IN_Shutdown (void); + + +void IN_JoyMove( void ); +void IN_StartupJoystick( void ); + +// GL subsystem +qboolean QGL_Init( const char *dllname ); +void QGL_EnableLogging( qboolean enable ); +void QGL_Shutdown( void ); + + + + + +// bk001130 - win32 +// void IN_JoystickCommands (void); + +char *strlwr (char *s); diff --git a/codemp/unix/linux_qgl.c b/codemp/unix/linux_qgl.c new file mode 100644 index 0000000..339bfe9 --- /dev/null +++ b/codemp/unix/linux_qgl.c @@ -0,0 +1,4132 @@ +/* +** 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 +*/ + +// bk001204 +#include +#include + + +#include +#include "../renderer/tr_local.h" +#include "unix_glw.h" + +// bk001129 - from cvs1.17 (mkv) +//#if defined(__FX__) +//#include +//#endif +//#include // bk010216 - FIXME: all of the above redundant? renderer/qgl.h + +#include + + +// bk001129 - from cvs1.17 (mkv) +#if defined(__FX__) +//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); +#endif + +//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; + +// bk001129 - from cvs1.17 (mkv) +#if defined(__FX__) + qfxMesaCreateContext = NULL; + qfxMesaCreateBestContext = NULL; + qfxMesaDestroyContext = NULL; + qfxMesaMakeCurrent = NULL; + qfxMesaGetCurrentContext = NULL; + qfxMesaSwapBuffers = NULL; +#endif + + 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 ( ( glw_state.OpenGLLib = dlopen( dllname, RTLD_LAZY|RTLD_GLOBAL ) ) == 0 ) + { + char fn[1024]; + // FILE *fp; // bk001204 - unused + extern uid_t saved_euid; // unix_main.c + + // 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" ); + 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" ); + +// bk001129 - from cvs1.17 (mkv) +#if defined(__FX__) + qfxMesaCreateContext = GPA("fxMesaCreateContext"); + qfxMesaCreateBestContext = GPA("fxMesaCreateBestContext"); + qfxMesaDestroyContext = GPA("fxMesaDestroyContext"); + qfxMesaMakeCurrent = GPA("fxMesaMakeCurrent"); + qfxMesaGetCurrentContext = GPA("fxMesaGetCurrentContext"); + qfxMesaSwapBuffers = GPA("fxMesaSwapBuffers"); +#endif + + 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 ) { + // bk001205 - fixed for new countdown + static qboolean isEnabled = qfalse; // init + + // return if we're already active + if ( isEnabled && enable ) { + // decrement log counter and stop if it has reached 0 + ri.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; + + // bk001205 - old code starts here + 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( "fs_basepath", "", 0 ); // FIXME: userdir? + assert(basedir); + Com_sprintf( buffer, sizeof(buffer), "%s/gl.log", basedir->string ); + glw_state.log_fp = fopen( buffer, "wt" ); + assert(glw_state.log_fp); + ri.Printf(PRINT_ALL, "QGL_EnableLogging(%d): writing %s\n", r_logFile->integer, buffer ); + + 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/codemp/unix/linux_snd.c b/codemp/unix/linux_snd.c new file mode 100644 index 0000000..793c5f1 --- /dev/null +++ b/codemp/unix/linux_snd.c @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __linux__ // rb0101023 - guard this +#include +#endif +#ifdef __FreeBSD__ // rb0101023 - added +#include +#endif +#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; // bk001204 - unused + struct audio_buf_info info; + int caps; + extern uid_t saved_euid; + + if (snd_inited) + return 1; + + 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 (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/codemp/unix/makefile b/codemp/unix/makefile new file mode 100644 index 0000000..4b12a1a --- /dev/null +++ b/codemp/unix/makefile @@ -0,0 +1,1505 @@ +# +# GNU Make required +# + +PLATFORM=$(shell uname|tr A-Z a-z) +PLATFORM_RELEASE=$(shell uname -r) + +### +### These paths are where you probably want to change things +### + +# Where we are building from (where the source code should be!) +MOUNT_DIR=../ + + +# Where we are building to, libMesaVoodooGL.so.3.3 should be here, etc. +# the demo pk3 file should be here in demoq3/pak0.pk3 or baseq3/pak0.pk3 +BDIR=$(MOUNT_DIR)/../run + + +# Build name +# BUILD_NAME=$(BUILD_NAME) +BUILD_NAME=jamp + + + +############################################################################# +## +## You shouldn't have to touch anything below here +## +############################################################################# + +BASEQ3_DIR=$(BDIR)/base + +BD=debug$(ARCH)$(GLIBC) +BR=release$(ARCH)$(GLIBC) +CDIR=$(MOUNT_DIR)/client +SDIR=$(MOUNT_DIR)/server +RDIR=$(MOUNT_DIR)/renderer +CMDIR=$(MOUNT_DIR)/qcommon +UDIR=$(MOUNT_DIR)/unix +GDIR=$(MOUNT_DIR)/game +CGDIR=$(MOUNT_DIR)/cgame +BAIDIR=$(GDIR) +BLIBDIR=$(MOUNT_DIR)/botlib +NDIR=$(MOUNT_DIR)/null +UIDIR=$(MOUNT_DIR)/ui +Q3UIDIR=$(MOUNT_DIR)/q3_ui +FTDIR=$(MOUNT_DIR)/ft2 +JPDIR=$(MOUNT_DIR)/jpeg-6 +SPLNDIR=$(MOUNT_DIR)/splines +GHOUL2DIR=$(MOUNT_DIR)/ghoul2 +IDIR=$(MOUNT_DIR)/icarus +ZLIB=$(MOUNT_DIR)/zlib32 +RMGDIR=$(MOUNT_DIR)/RMG + +############################################################################# +# SETUP AND BUILD -- LINUX +############################################################################# + +## Defaults +DLL_ONLY=false + + ifneq (,$(findstring libc6,$(shell if [ -e /lib/libc.so.6* ];then echo libc6;fi))) + GLIBC=-glibc + else + GLIBC= + endif #libc6 test + + MESADIR=../Mesa/ + ARCH=i386 + RPMARCH=i386 + VENDOR=unknown + DLL_ONLY=false + + # bk001205: no mo' -I/usr/include/glide, no FX + # bk001205: no mo' -Dstricmp=strcasecmp, see q_shared.h + #BASE_CFLAGS = -pipe -fsigned-char -x c++ -D_JK2 -D_M_IX86 -I/home/drews/STLport-4.5.3/stlport -I/opt/intel/compiler50/ia32/include + #BASE_CFLAGS = -pipe -fsigned-char -Kc++ -D_JK2 -D_M_IX86 -I/opt/intel/compiler50/ia32/include + BASE_CFLAGS = -Kc++ -D_JK2 -D_M_IX86 -w -I/opt/intel/compiler50/ia32/include + # rcg010216: DLL_ONLY for PPC + ifeq ($(strip $(DLL_ONLY)),true) + BASE_CFLAGS += -DDLL_ONLY + endif + + + #GL_CFLAGS = -I$(MESADIR)/include -I/usr/X11R6/include + GL_CFAGS = -I/usr/X11R6/include + + # bk001204 - need -O for -Wall for uninitialized + # bk001205 - took out -O to get assertions (NDEBUG) + # bk001206 - MALLOC_CHECK in addition to ZONE_DEBUG + # TTimo 03/30/2001 temporary took out -Werror for initial merge + DEBUG_CFLAGS=$(BASE_CFLAGS) -g +# DEBUG_CFLAGS=$(BASE_CFLAGS) -g -Wall -Werror -O + ifeq ($(ARCH),axp) + CC=pgcc + RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O2 -unroll + else + ifeq ($(ARCH),ppc) + NEWPGCC=/loki/global/ppc/bin/gcc + CC=$(NEWPGCC) + RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O2 -pipe -unroll + else + #NEWPGCC=/usr/local/gcc-2.95.2/bin/gcc # bk001205 + #NEWPGCC=/loki/global/x86/bin/gcc + #NEWPGCC=/usr/bin/gcc + #NEWPGCC=/usr/local/bin/gcc + NEWPGCC=/opt/intel/compiler50/ia32/bin/icc + CC=$(shell if [ -f $(NEWPGCC) ]; then echo $(NEWPGCC); else echo pgcc; fi ) + CXX=/usr/bin/g++ +# TTimo: legacy RELEASE_CFLAGS +# NOTE: the -fomit-frame-pointer option leads to an unstable binary on my test box if it was built on the main box +# but building on the Mdk 7.2 baseline seems to work + RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O2 -unroll -tpp6 +# TTimo: use this for building on P3 gcc 2.95.3 libc2.2 for all targets (experimental! -fomit-fram-pointer removed) +# RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O6 -mcpu=pentiumpro -march=pentium -pipe -ffast-math -malign-loops=2 -malign-jumps=2 -malign-functions=2 -fno-strict-aliasing -fstrength-reduce + endif + endif + + LIBEXT=a + + SHLIBEXT=so + SHLIBCFLAGS=-fPIC + SHLIBLDFLAGS=-shared $(LDFLAGS) + + ARFLAGS=ar rv + RANLIB=ranlib + + THREAD_LDFLAGS=-lpthread + #LDFLAGS=/opt/sxl/lib/sxlgcc3.a -lpthread -ldl -lm -lstdc++ -static -Wl --gc-sections + LDFLAGS=-ldl -lm + GLLDFLAGS=-L/usr/X11R6/lib -L$(MESADIR)/lib -lX11 -lXext -lXxf86dga -lXxf86vm + + TARGETS=\ + $(B)/$(PLATFORM)jampded \ + $(B)/$(BUILD_NAME)game$(ARCH)-debug.so + +DO_CC=$(CC) $(CFLAGS) -o $@ -c $< +DO_CXX=$(CXX) $(CFLAGS) -o $@ -c $< +DO_SMP_CC=$(CC) $(CFLAGS) -DSMP -o $@ -c $< +DO_BOT_CC=$(CC) $(CFLAGS) -DBOTLIB -o $@ -c $< # $(SHLIBCFLAGS) # bk001212 +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 $< +DO_NASM=nasm -f elf -o $@ $< +DO_DED_CC=$(CC) -DDEDICATED -DC_ONLY $(CFLAGS) -use_msasm -o $@ -c $< +DO_DED_CC2=$(CC) -DDEDICATED -DC_ONLY $(CFLAGS) -o $@ -c $< +DO_DED_CPP=$(CXX) -DDEDICATED -DC_ONLY $(CFLAGS) -o $@ -c $< + +#DO_LCC=$(LCC) -o $@ -S -Wf-target=bytecode -Wf-g -DQ3_VM -I$(CGDIR) -I$(GDIR) -I$(UIDIR) $< + +#### DEFAULT TARGET +default:build_debug + +debug: build_debug +release: build_release + +build_debug: + $(MAKE) targets B=$(BD) CFLAGS="$(DEBUG_CFLAGS)" + +build_release: + $(MAKE) targets B=$(BR) CFLAGS="$(RELEASE_CFLAGS)" + +#Build both debug and release builds +all:build_debug build_release + +targets:makedirs $(TARGETS) + + +makedirs: + @if [ ! -d $(B) ];then mkdir $(B);fi +# @if [ ! -d $(B)/client ];then mkdir $(B)/client;fi + @if [ ! -d $(B)/ded ];then mkdir $(B)/ded;fi +# @if [ ! -d $(B)/ref ];then mkdir $(B)/ref;fi +# @if [ ! -d $(B)/ft2 ];then mkdir $(B)/ft2;fi + @if [ ! -d $(B)/baseq3 ];then mkdir $(B)/baseq3;fi +# @if [ ! -d $(B)/baseq3/cgame ];then mkdir $(B)/baseq3/cgame;fi + @if [ ! -d $(B)/baseq3/game ];then mkdir $(B)/baseq3/game;fi +# @if [ ! -d $(B)/baseq3/ui ];then mkdir $(B)/baseq3/ui;fi +# @if [ ! -d $(B)/baseq3/vm ];then mkdir $(B)/baseq3/vm;fi +# @if [ ! -d $(B)/missionpack ];then mkdir $(B)/missionpack;fi +# @if [ ! -d $(B)/missionpack/cgame ];then mkdir $(B)/missionpack/cgame;fi +# @if [ ! -d $(B)/missionpack/game ];then mkdir $(B)/missionpack/game;fi +# @if [ ! -d $(B)/missionpack/ui ];then mkdir $(B)/missionpack/ui;fi +# @if [ ! -d $(B)/missionpack/vm ];then mkdir $(B)/missionpack/vm;fi +# @if [ ! -d $(B)/q3static ];then mkdir $(B)/q3static;fi + + +############################################################################# +# DEDICATED SERVER +############################################################################# + +Q3DOBJ = \ + $(B)/ded/sv_bot.o \ + $(B)/ded/sv_client.o \ + $(B)/ded/sv_ccmds.o \ + $(B)/ded/sv_game.o \ + $(B)/ded/sv_init.o \ + $(B)/ded/sv_main.o \ + $(B)/ded/sv_net_chan.o \ + $(B)/ded/sv_snapshot.o \ + $(B)/ded/sv_world.o \ + \ + $(B)/ded/cm_load.o \ + $(B)/ded/stringed_ingame.o \ + $(B)/ded/stringed_interface.o \ + $(B)/ded/GenericParser2.o \ + $(B)/ded/cm_patch.o \ + $(B)/ded/cm_polylib.o \ + $(B)/ded/cm_test.o \ + $(B)/ded/cm_trace.o \ + $(B)/ded/common.o \ + $(B)/ded/cvar.o \ + $(B)/ded/md4.o \ + $(B)/ded/msg.o \ + $(B)/ded/net_chan.o \ + $(B)/ded/huffman.o \ + \ + $(B)/ded/q_math.o \ + $(B)/ded/q_shared.o \ + $(B)/ded/z_memman_pc.o \ + \ + $(B)/ded/inflate.o \ + $(B)/ded/deflate.o \ + $(B)/ded/zipcommon.o \ + \ + $(B)/ded/unzip.o \ + $(B)/ded/vm.o \ + $(B)/ded/vm_interpreted.o \ + \ + $(B)/ded/be_aas_bspq3.o \ + $(B)/ded/be_aas_cluster.o \ + $(B)/ded/be_aas_debug.o \ + $(B)/ded/be_aas_entity.o \ + $(B)/ded/be_aas_file.o \ + $(B)/ded/be_aas_main.o \ + $(B)/ded/be_aas_move.o \ + $(B)/ded/be_aas_optimize.o \ + $(B)/ded/be_aas_reach.o \ + $(B)/ded/be_aas_route.o \ + $(B)/ded/be_aas_routealt.o \ + $(B)/ded/be_aas_sample.o \ + $(B)/ded/be_ai_char.o \ + $(B)/ded/be_ai_chat.o \ + $(B)/ded/be_ai_gen.o \ + $(B)/ded/be_ai_goal.o \ + $(B)/ded/be_ai_move.o \ + $(B)/ded/be_ai_weap.o \ + $(B)/ded/be_ai_weight.o \ + $(B)/ded/be_ea.o \ + $(B)/ded/be_interface.o \ + $(B)/ded/l_crc.o \ + $(B)/ded/l_libvar.o \ + $(B)/ded/l_log.o \ + $(B)/ded/l_memory.o \ + $(B)/ded/l_precomp.o \ + $(B)/ded/l_script.o \ + $(B)/ded/l_struct.o \ + \ + $(B)/ded/linux_common.o \ + $(B)/ded/unix_main.o \ + $(B)/ded/unix_net.o \ + $(B)/ded/unix_shared.o \ + \ + $(B)/ded/G2_API.o \ + $(B)/ded/G2_bolts.o \ + $(B)/ded/G2_surfaces.o \ + $(B)/ded/G2_misc.o \ + \ + $(B)/ded/null_client.o \ + $(B)/ded/null_input.o \ + $(B)/ded/null_snddma.o \ + $(B)/ded/null_glimp.o \ + $(B)/ded/null_renderer.o \ + \ + $(B)/ded/BlockStream.o \ + $(B)/ded/GameInterface.o \ + $(B)/ded/Instance.o \ + $(B)/ded/Interface.o \ + $(B)/ded/Memory.o \ + $(B)/ded/Q3_Interface.o \ + $(B)/ded/Q3_Registers.o \ + $(B)/ded/Sequence.o \ + $(B)/ded/Sequencer.o \ + $(B)/ded/TaskManager.o \ + \ + $(B)/ded/tr_model.o \ + $(B)/ded/tr_image.o \ + $(B)/ded/RoffSystem.o \ + $(B)/ded/tr_ghoul2.o \ + $(B)/ded/matcomp.o \ + $(B)/ded/tr_init.o \ + $(B)/ded/tr_main.o \ + $(B)/ded/tr_backend.o \ + $(B)/ded/tr_mesh.o \ + $(B)/ded/G2_bones.o \ + $(B)/ded/tr_shader.o \ + $(B)/ded/cmd_pc.o \ + $(B)/ded/navigator.o \ + $(B)/ded/gameCallbacks.o \ + $(B)/ded/files_common.o \ + $(B)/ded/cmd_common.o \ + $(B)/ded/files_linux.o \ + $(B)/ded/RM_Manager.o \ + $(B)/ded/RM_Mission.o \ + $(B)/ded/RM_Instance.o \ + $(B)/ded/RM_InstanceFile.o \ + $(B)/ded/RM_Instance_BSP.o \ + $(B)/ded/RM_Instance_Group.o \ + $(B)/ded/RM_Instance_Void.o \ + $(B)/ded/RM_Instance_Random.o \ + $(B)/ded/RM_Objective.o \ + $(B)/ded/RM_Terrain.o \ + $(B)/ded/RM_Path.o \ + $(B)/ded/RM_Area.o \ + $(B)/ded/cm_terrain.o \ + $(B)/ded/cm_randomterrain.o \ + $(B)/ded/cm_shader.o \ +# $(B)/ded/tr_terrain.o \ +# \ +# $(B)/ded/null_main.o \ +# $(B)/ded/null_net.o \ +# $(B)/ded/tr_cmds.o \ +# $(B)/ded/tr_animation.o \ +# $(B)/ded/tr_bsp.o \ +# $(B)/ded/tr_curve.o \ +# $(B)/ded/tr_flares.o \ +# $(B)/ded/tr_font.o \ +# $(B)/ded/tr_light.o \ +# $(B)/ded/tr_marks.o \ +# $(B)/ded/tr_noise.o \ +# $(B)/ded/tr_quicksprite.o \ +# $(B)/ded/tr_scene.o \ +# $(B)/ded/tr_shade_calc.o \ +# $(B)/ded/tr_shade.o \ +# $(B)/ded/tr_shadows.o \ +# $(B)/ded/tr_surfacesprites.o \ +# $(B)/ded/tr_world.o \ +# $(B)/ded/tr_worldeffects.o \ +# \ +# $(B)/ded/linux_glimp.o \ +# $(B)/ded/tr_image.o +# $(B)/ded/linux_qgl.o +# $(B)/ded/snapvector.o \ +# $(B)/ded/tr_main.o +# $(B)/ded/tr_sky.o + +#ifeq ($(ARCH),i386) + Q3DOBJ += $(B)/ded/vm_x86.o $(B)/ded/ftol.o $(B)/ded/snapvector.o +#endif + +ifeq ($(ARCH),ppc) + ifeq ($(DLL_ONLY),false) + Q3DOBJ += $(B)/ded/vm_ppc.o + endif +endif + +$(B)/$(PLATFORM)jampded : $(Q3DOBJ) + $(CC) -o $@ $(Q3DOBJ) $(LDFLAGS) + +$(B)/$(BUILD_NAME)game$(ARCH)-debug.so : $(Q3GOBJ) + $(CC) -o $@ $(Q3GOBJ) $(LDFLAGS) -shared + +$(B)/ded/sv_bot.o : $(SDIR)/sv_bot.cpp; $(DO_DED_CC) +$(B)/ded/sv_client.o : $(SDIR)/sv_client.cpp; $(DO_DED_CC) +$(B)/ded/sv_ccmds.o : $(SDIR)/sv_ccmds.cpp; $(DO_DED_CC) +$(B)/ded/sv_game.o : $(SDIR)/sv_game.cpp; $(DO_DED_CC) +$(B)/ded/sv_init.o : $(SDIR)/sv_init.cpp; $(DO_DED_CC) +$(B)/ded/sv_main.o : $(SDIR)/sv_main.cpp; $(DO_DED_CC) +$(B)/ded/sv_net_chan.o : $(SDIR)/sv_net_chan.cpp; $(DO_DED_CC) +$(B)/ded/sv_snapshot.o : $(SDIR)/sv_snapshot.cpp; $(DO_DED_CC) +$(B)/ded/sv_world.o : $(SDIR)/sv_world.cpp; $(DO_DED_CC) +$(B)/ded/gameCallbacks.o : $(SDIR)/NPCNav/gameCallbacks.cpp; $(DO_DED_CC) +$(B)/ded/navigator.o: $(SDIR)/NPCNav/navigator.cpp; $(DO_DED_CC) + +$(B)/ded/cm_load.o : $(CMDIR)/cm_load.cpp; $(DO_DED_CC) +$(B)/ded/stringed_ingame.o : $(CMDIR)/stringed_ingame.cpp; $(DO_DED_CC) +$(B)/ded/stringed_interface.o : $(CMDIR)/stringed_interface.cpp; $(DO_DED_CC) +$(B)/ded/GenericParser2.o : $(CMDIR)/GenericParser2.cpp; $(DO_DED_CC) +$(B)/ded/cm_shader.o : $(CMDIR)/cm_shader.cpp; $(DO_DED_CC) +$(B)/ded/cm_polylib.o : $(CMDIR)/cm_polylib.cpp; $(DO_DED_CC) +$(B)/ded/cm_test.o : $(CMDIR)/cm_test.cpp; $(DO_DED_CC) +$(B)/ded/cm_trace.o : $(CMDIR)/cm_trace.cpp; $(DO_DED_CC) +$(B)/ded/cm_patch.o : $(CMDIR)/cm_patch.cpp; $(DO_DED_CC) +$(B)/ded/common.o : $(CMDIR)/common.cpp; $(DO_DED_CC) +$(B)/ded/cvar.o : $(CMDIR)/cvar.cpp; $(DO_DED_CC) +$(B)/ded/files.o : $(CMDIR)/files.cpp; $(DO_DED_CC) +$(B)/ded/md4.o : $(CMDIR)/md4.cpp; $(DO_DED_CC) +$(B)/ded/msg.o : $(CMDIR)/msg.cpp; $(DO_DED_CC) +$(B)/ded/net_chan.o : $(CMDIR)/net_chan.cpp; $(DO_DED_CC) +$(B)/ded/huffman.o : $(CMDIR)/huffman.cpp; $(DO_DED_CC) +$(B)/ded/q_shared.o : $(CMDIR)/q_shared.cpp; $(DO_DED_CC) +$(B)/ded/q_math.o : $(GDIR)/q_math.c; $(DO_DED_CC) +$(B)/ded/z_memman_pc.o : $(CMDIR)/z_memman_pc.cpp; $(DO_DED_CC) +$(B)/ded/files_common.o : $(CMDIR)/files_common.cpp; $(DO_DED_CC) +$(B)/ded/cmd_common.o : $(CMDIR)/cmd_common.cpp; $(DO_DED_CC) +$(B)/ded/files_linux.o : $(UDIR)/files_linux.cpp; $(DO_DED_CC) + +$(B)/ded/RM_Manager.o : $(RMGDIR)/RM_Manager.cpp; $(DO_DED_CC) +$(B)/ded/RM_Mission.o : $(RMGDIR)/RM_Mission.cpp; $(DO_DED_CC) +$(B)/ded/RM_Instance.o: $(RMGDIR)/RM_Instance.cpp; $(DO_DED_CC) +$(B)/ded/RM_InstanceFile.o : $(RMGDIR)/RM_InstanceFile.cpp; $(DO_DED_CC) +$(B)/ded/RM_Instance_Void.o : $(RMGDIR)/RM_Instance_Void.cpp; $(DO_DED_CC) +$(B)/ded/RM_Instance_Random.o : $(RMGDIR)/RM_Instance_Random.cpp; $(DO_DED_CC) +$(B)/ded/RM_Instance_Group.o : $(RMGDIR)/RM_Instance_Group.cpp; $(DO_DED_CC) +$(B)/ded/RM_Instance_BSP.o : $(RMGDIR)/RM_Instance_BSP.cpp; $(DO_DED_CC) +$(B)/ded/RM_Objective.o : $(RMGDIR)/RM_Objective.cpp; $(DO_DED_CC) +$(B)/ded/RM_Path.o : $(RMGDIR)/RM_Path.cpp; $(DO_DED_CC) +$(B)/ded/RM_Area.o : $(RMGDIR)/RM_Area.cpp; $(DO_DED_CC) +$(B)/ded/RM_Terrain.o : $(RMGDIR)/RM_Terrain.cpp; $(DO_DED_CC) +$(B)/ded/tr_terrain.o : $(RDIR)/tr_terrain.cpp; $(DO_DED_CC) +$(B)/ded/cm_terrain.o : $(CMDIR)/cm_terrain.cpp; $(DO_DED_CC) +$(B)/ded/cm_randomterrain.o : $(CMDIR)/cm_randomterrain.cpp; $(DO_DED_CC) + +$(B)/ded/G2_API.o : $(GHOUL2DIR)/G2_API.cpp; $(DO_DED_CC) +$(B)/ded/G2_bolts.o : $(GHOUL2DIR)/G2_bolts.cpp; $(DO_DED_CC) +$(B)/ded/G2_bones.o : $(GHOUL2DIR)/G2_bones.cpp; $(DO_DED_CC) +$(B)/ded/G2_misc.o : $(GHOUL2DIR)/G2_misc.cpp; $(DO_DED_CC) +$(B)/ded/G2_surfaces.o : $(GHOUL2DIR)/G2_surfaces.cpp; $(DO_DED_CC) + +$(B)/ded/RoffSystem.o : $(CMDIR)/RoffSystem.cpp; $(DO_DED_CC) + +$(B)/ded/inflate.o : $(ZLIB)/inflate.cpp; $(DO_DED_CC) +$(B)/ded/deflate.o : $(ZLIB)/deflate.cpp; $(DO_DED_CC) +$(B)/ded/zipcommon.o : $(ZLIB)/zipcommon.cpp; $(DO_DED_CC) + +$(B)/ded/tr_model.o : $(RDIR)/tr_model.cpp; $(DO_DED_CC) +$(B)/ded/tr_image.o : $(RDIR)/tr_image.cpp; $(DO_DED_CC) +$(B)/ded/tr_ghoul2.o : $(RDIR)/tr_ghoul2.cpp; $(DO_DED_CC) +$(B)/ded/tr_shader.o : $(RDIR)/tr_shader.cpp; $(DO_DED_CC) +$(B)/ded/tr_sky.o : $(RDIR)/tr_shader.cpp; $(DO_DED_CC) +$(B)/ded/tr_cmds.o : $(RDIR)/tr_cmds.cpp; $(DO_DED_CC) +$(B)/ded/tr_backend.o : $(RDIR)/tr_backend.cpp; $(DO_DED_CC) +$(B)/ded/tr_animation.o : $(RDIR)/tr_animation.cpp; $(DO_DED_CC) +$(B)/ded/tr_bsp.o : $(RDIR)/tr_bsp.cpp; $(DO_DED_CC) +$(B)/ded/tr_curve.o : $(RDIR)/tr_curve.cpp; $(DO_DED_CC) +$(B)/ded/tr_flares.o : $(RDIR)/tr_flares.cpp; $(DO_DED_CC) +$(B)/ded/tr_font.o : $(RDIR)/tr_font.cpp; $(DO_DED_CC) +$(B)/ded/tr_init.o : $(RDIR)/tr_init.cpp; $(DO_DED_CC) +$(B)/ded/tr_light.o : $(RDIR)/tr_light.cpp; $(DO_DED_CC) +$(B)/ded/tr_main.o : $(RDIR)/tr_main.cpp; $(DO_DED_CC) +$(B)/ded/tr_marks.o : $(RDIR)/tr_marks.cpp; $(DO_DED_CC) +$(B)/ded/tr_mesh.o : $(RDIR)/tr_mesh.cpp; $(DO_DED_CC) +$(B)/ded/tr_noise.o : $(RDIR)/tr_noise.cpp; $(DO_DED_CC) +$(B)/ded/tr_quicksprite.o : $(RDIR)/tr_quicksprite.cpp; $(DO_DED_CC) +$(B)/ded/tr_scene.o : $(RDIR)/tr_scene.cpp; $(DO_DED_CC) +$(B)/ded/tr_shade.o : $(RDIR)/tr_shade.cpp; $(DO_DED_CC) +$(B)/ded/tr_shade_calc.o : $(RDIR)/tr_shade_calc.cpp; $(DO_DED_CC) +$(B)/ded/tr_shadows.o : $(RDIR)/tr_shadows.cpp; $(DO_DED_CC) +$(B)/ded/tr_surface.o : $(RDIR)/tr_surface.cpp; $(DO_DED_CC) +$(B)/ded/tr_surfacesprites.o : $(RDIR)/tr_surfacesprites.cpp; $(DO_DED_CC) +$(B)/ded/tr_world.o : $(RDIR)/tr_world.cpp; $(DO_DED_CC) +$(B)/ded/tr_worldeffects.o : $(RDIR)/tr_worldeffects.cpp; $(DO_DED_CC) +$(B)/ded/matcomp.o : $(RDIR)/matcomp.c; $(DO_DED_CC) + + +$(B)/ded/be_aas_bspq3.o : $(BLIBDIR)/be_aas_bspq3.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_cluster.o : $(BLIBDIR)/be_aas_cluster.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_debug.o : $(BLIBDIR)/be_aas_debug.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_entity.o : $(BLIBDIR)/be_aas_entity.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_file.o : $(BLIBDIR)/be_aas_file.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_main.o : $(BLIBDIR)/be_aas_main.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_move.o : $(BLIBDIR)/be_aas_move.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_optimize.o : $(BLIBDIR)/be_aas_optimize.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_reach.o : $(BLIBDIR)/be_aas_reach.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_route.o : $(BLIBDIR)/be_aas_route.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_routealt.o : $(BLIBDIR)/be_aas_routealt.cpp; $(DO_BOT_CC) +$(B)/ded/be_aas_sample.o : $(BLIBDIR)/be_aas_sample.cpp; $(DO_BOT_CC) +$(B)/ded/be_ai_char.o : $(BLIBDIR)/be_ai_char.cpp; $(DO_BOT_CC) +$(B)/ded/be_ai_chat.o : $(BLIBDIR)/be_ai_chat.cpp; $(DO_BOT_CC) +$(B)/ded/be_ai_gen.o : $(BLIBDIR)/be_ai_gen.cpp; $(DO_BOT_CC) +$(B)/ded/be_ai_goal.o : $(BLIBDIR)/be_ai_goal.cpp; $(DO_BOT_CC) +$(B)/ded/be_ai_move.o : $(BLIBDIR)/be_ai_move.cpp; $(DO_BOT_CC) +$(B)/ded/be_ai_weap.o : $(BLIBDIR)/be_ai_weap.cpp; $(DO_BOT_CC) +$(B)/ded/be_ai_weight.o : $(BLIBDIR)/be_ai_weight.cpp; $(DO_BOT_CC) +$(B)/ded/be_ea.o : $(BLIBDIR)/be_ea.cpp; $(DO_BOT_CC) +$(B)/ded/be_interface.o : $(BLIBDIR)/be_interface.cpp; $(DO_BOT_CC) +$(B)/ded/l_crc.o : $(BLIBDIR)/l_crc.cpp; $(DO_BOT_CC) +$(B)/ded/l_libvar.o : $(BLIBDIR)/l_libvar.cpp; $(DO_BOT_CC) +$(B)/ded/l_log.o : $(BLIBDIR)/l_log.cpp; $(DO_BOT_CC) +$(B)/ded/l_memory.o : $(BLIBDIR)/l_memory.cpp; $(DO_BOT_CC) +$(B)/ded/l_precomp.o : $(BLIBDIR)/l_precomp.cpp; $(DO_BOT_CC) +$(B)/ded/l_script.o : $(BLIBDIR)/l_script.cpp; $(DO_BOT_CC) +$(B)/ded/l_struct.o : $(BLIBDIR)/l_struct.cpp; $(DO_BOT_CC) + +$(B)/ded/linux_common.o : $(UDIR)/linux_common.c; $(DO_CC) +$(B)/ded/linux_glimp.o : $(UDIR)/linux_glimp.c; $(DO_DED_CC) +$(B)/ded/unix_main.o : $(UDIR)/unix_main.c; $(DO_DED_CC2) +$(B)/ded/unix_net.o : $(UDIR)/unix_net.c; $(DO_DED_CC) +$(B)/ded/unix_shared.o : $(UDIR)/unix_shared.cpp; $(DO_DED_CC) +$(B)/ded/linux_qgl.o : $(UDIR)/linux_qgl.c; $(DO_DED_CC) +$(B)/ded/null_client.o : $(NDIR)/null_client.cpp; $(DO_DED_CC) +$(B)/ded/null_input.o : $(NDIR)/null_input.cpp; $(DO_DED_CC) +$(B)/ded/null_snddma.o : $(NDIR)/null_snddma.cpp; $(DO_DED_CC) +$(B)/ded/null_glimp.o : $(NDIR)/null_glimp.cpp; $(DO_DED_CC) +$(B)/ded/null_main.o : $(NDIR)/null_main.cpp; $(DO_DED_CC) +$(B)/ded/null_net.o : $(NDIR)/null_net.cpp; $(DO_DED_CC) +$(B)/ded/null_renderer.o : $(NDIR)/null_renderer.cpp; $(DO_DED_CC) +$(B)/ded/unzip.o : $(CMDIR)/unzip.cpp; $(DO_DED_CC) +$(B)/ded/vm.o : $(CMDIR)/vm.cpp; $(DO_DED_CC) +$(B)/ded/vm_interpreted.o : $(CMDIR)/vm_interpreted.cpp; $(DO_DED_CC) +$(B)/ded/cmd_pc.o : $(CMDIR)/cmd_pc.cpp; $(DO_DED_CC) + +$(B)/ded/BlockStream.o : $(IDIR)/BlockStream.cpp; $(DO_DED_CC) +$(B)/ded/GameInterface.o : $(IDIR)/GameInterface.cpp; $(DO_DED_CC) +$(B)/ded/Instance.o : $(IDIR)/Instance.cpp; $(DO_DED_CC) +$(B)/ded/Interface.o : $(IDIR)/Interface.cpp; $(DO_DED_CC) +$(B)/ded/Memory.o : $(IDIR)/Memory.cpp; $(DO_DED_CC) +$(B)/ded/Q3_Interface.o : $(IDIR)/Q3_Interface.cpp; $(DO_DED_CC) +$(B)/ded/Q3_Registers.o : $(IDIR)/Q3_Registers.cpp; $(DO_DED_CC) +$(B)/ded/Sequence.o: $(IDIR)/Sequence.cpp; $(DO_DED_CC) +$(B)/ded/Sequencer.o : $(IDIR)/Sequencer.cpp; $(DO_DED_CC) +$(B)/ded/TaskManager.o: $(IDIR)/TaskManager.cpp; $(DO_DED_CC) + +#ifeq ($(ARCH),i386) +$(B)/ded/vm_x86.o : $(CMDIR)/vm_x86.cpp; $(DO_DED_CC2) +$(B)/ded/ftol.o : $(UDIR)/ftol.nasm; $(DO_NASM) +$(B)/ded/snapvector.o : $(UDIR)/snapvector.nasm; $(DO_NASM) +#endif + +ifeq ($(ARCH),ppc) +ifeq ($(DLL_ONLY),false) +$(B)/ded/vm_ppc.o : $(CMDIR)/vm_ppc.c; $(DO_DED_CC) +endif +endif + + +############################################################################# +## QVM +############################################################################# + +$(B)/baseq3/vm/cgame.qvm: + cd $(CGDIR) && ./cgame.sh + mv /tmp/quake3/baseq3/vm/cgame.qvm $@ + +$(B)/baseq3/vm/ui.qvm: + cd $(Q3UIDIR) && ./q3_ui.sh + mv /tmp/quake3/baseq3/vm/ui.qvm $@ + +$(B)/baseq3/vm/qagame.qvm: + cd $(GDIR) && ./game.sh + mv /tmp/quake3/baseq3/vm/qagame.qvm $@ + +$(B)/missionpack/vm/cgame.qvm: + cd $(CGDIR) && ./cgame_ta.sh + mv /tmp/quake3/missionpack/vm/cgame.qvm $@ + +$(B)/missionpack/vm/qagame.qvm: + cd $(GDIR) && ./game_ta.sh + mv /tmp/quake3/missionpack/vm/qagame.qvm $@ + +$(B)/missionpack/vm/ui.qvm: + cd $(UIDIR) && ./ui.sh + mv /tmp/quake3/missionpack/vm/ui.qvm $@ + + + +############################################################################# +## BASEQ3 CGAME +############################################################################# + +Q3CGOBJ = \ + $(B)/baseq3/cgame/bg_misc.o \ + $(B)/baseq3/cgame/bg_pmove.o \ + $(B)/baseq3/cgame/bg_slidemove.o \ + $(B)/baseq3/cgame/cg_consolecmds.o \ + $(B)/baseq3/cgame/cg_draw.o \ + $(B)/baseq3/cgame/cg_drawtools.o \ + $(B)/baseq3/cgame/cg_effects.o \ + $(B)/baseq3/cgame/cg_ents.o \ + $(B)/baseq3/cgame/cg_event.o \ + $(B)/baseq3/cgame/cg_info.o \ + $(B)/baseq3/cgame/cg_localents.o \ + $(B)/baseq3/cgame/cg_main.o \ + $(B)/baseq3/cgame/cg_marks.o \ + $(B)/baseq3/cgame/cg_players.o \ + $(B)/baseq3/cgame/cg_playerstate.o \ + $(B)/baseq3/cgame/cg_predict.o \ + $(B)/baseq3/cgame/cg_scoreboard.o \ + $(B)/baseq3/cgame/cg_servercmds.o \ + $(B)/baseq3/cgame/cg_snapshot.o \ + $(B)/baseq3/cgame/cg_syscalls.o \ + $(B)/baseq3/cgame/cg_view.o \ + $(B)/baseq3/cgame/cg_weapons.o \ + $(B)/baseq3/cgame/q_math.o \ + $(B)/baseq3/cgame/q_shared.o + +$(B)/baseq3/cgame$(ARCH).$(SHLIBEXT) : $(Q3CGOBJ) + $(CC) $(SHLIBLDFLAGS) -o $@ $(Q3CGOBJ) + +$(B)/baseq3/cgame/bg_misc.o : $(GDIR)/bg_misc.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/bg_pmove.o : $(GDIR)/bg_pmove.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/bg_slidemove.o : $(GDIR)/bg_slidemove.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_consolecmds.o : $(CGDIR)/cg_consolecmds.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_draw.o : $(CGDIR)/cg_draw.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_drawtools.o : $(CGDIR)/cg_drawtools.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_effects.o : $(CGDIR)/cg_effects.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_ents.o : $(CGDIR)/cg_ents.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_event.o : $(CGDIR)/cg_event.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_info.o : $(CGDIR)/cg_info.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_localents.o : $(CGDIR)/cg_localents.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_main.o : $(CGDIR)/cg_main.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_marks.o : $(CGDIR)/cg_marks.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_players.o : $(CGDIR)/cg_players.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_playerstate.o : $(CGDIR)/cg_playerstate.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_predict.o : $(CGDIR)/cg_predict.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_scoreboard.o : $(CGDIR)/cg_scoreboard.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_servercmds.o : $(CGDIR)/cg_servercmds.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_snapshot.o : $(CGDIR)/cg_snapshot.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_syscalls.o : $(CGDIR)/cg_syscalls.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_view.o : $(CGDIR)/cg_view.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/cg_weapons.o : $(CGDIR)/cg_weapons.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/q_math.o : $(GDIR)/q_math.c; $(DO_SHLIB_CC) +$(B)/baseq3/cgame/q_shared.o : $(GDIR)/q_shared.c; $(DO_SHLIB_CC) + +############################################################################# +## BASEQ3 GAME +############################################################################# + +Q3GOBJ = \ + $(B)/baseq3/game/ai_chat.o \ + $(B)/baseq3/game/ai_cmd.o \ + $(B)/baseq3/game/ai_dmnet.o \ + $(B)/baseq3/game/ai_dmq3.o \ + $(B)/baseq3/game/ai_main.o \ + $(B)/baseq3/game/ai_team.o \ + $(B)/baseq3/game/ai_vcmd.o \ + $(B)/baseq3/game/bg_misc.o \ + $(B)/baseq3/game/bg_pmove.o \ + $(B)/baseq3/game/bg_slidemove.o \ + $(B)/baseq3/game/g_active.o \ + $(B)/baseq3/game/g_arenas.o \ + $(B)/baseq3/game/g_bot.o \ + $(B)/baseq3/game/g_client.o \ + $(B)/baseq3/game/g_cmds.o \ + $(B)/baseq3/game/g_combat.o \ + $(B)/baseq3/game/g_items.o \ + $(B)/baseq3/game/g_main.o \ + $(B)/baseq3/game/g_mem.o \ + $(B)/baseq3/game/g_misc.o \ + $(B)/baseq3/game/g_missile.o \ + $(B)/baseq3/game/g_mover.o \ + $(B)/baseq3/game/g_session.o \ + $(B)/baseq3/game/g_spawn.o \ + $(B)/baseq3/game/g_svcmds.o \ + $(B)/baseq3/game/g_syscalls.o \ + $(B)/baseq3/game/g_target.o \ + $(B)/baseq3/game/g_team.o \ + $(B)/baseq3/game/g_trigger.o \ + $(B)/baseq3/game/g_utils.o \ + $(B)/baseq3/game/g_weapon.o \ + \ + $(B)/baseq3/game/q_math.o \ + $(B)/baseq3/game/q_shared.o + +$(B)/baseq3/qagame$(ARCH).$(SHLIBEXT) : $(Q3GOBJ) + $(CC) $(SHLIBLDFLAGS) -o $@ $(Q3GOBJ) + +$(B)/baseq3/game/ai_chat.o : $(GDIR)/ai_chat.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/ai_cmd.o : $(GDIR)/ai_cmd.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/ai_dmnet.o : $(GDIR)/ai_dmnet.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/ai_dmq3.o : $(GDIR)/ai_dmq3.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/ai_main.o : $(GDIR)/ai_main.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/ai_team.o : $(GDIR)/ai_team.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/ai_vcmd.o : $(GDIR)/ai_vcmd.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/bg_misc.o : $(GDIR)/bg_misc.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/bg_pmove.o : $(GDIR)/bg_pmove.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/bg_slidemove.o : $(GDIR)/bg_slidemove.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_active.o : $(GDIR)/g_active.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_arenas.o : $(GDIR)/g_arenas.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_bot.o : $(GDIR)/g_bot.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_client.o : $(GDIR)/g_client.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_cmds.o : $(GDIR)/g_cmds.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_combat.o : $(GDIR)/g_combat.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_items.o : $(GDIR)/g_items.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_main.o : $(GDIR)/g_main.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_mem.o : $(GDIR)/g_mem.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_misc.o : $(GDIR)/g_misc.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_missile.o : $(GDIR)/g_missile.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_mover.o : $(GDIR)/g_mover.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_session.o : $(GDIR)/g_session.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_spawn.o : $(GDIR)/g_spawn.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_svcmds.o : $(GDIR)/g_svcmds.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_syscalls.o : $(GDIR)/g_syscalls.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_target.o : $(GDIR)/g_target.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_team.o : $(GDIR)/g_team.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_trigger.o : $(GDIR)/g_trigger.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_utils.o : $(GDIR)/g_utils.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/g_weapon.o : $(GDIR)/g_weapon.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/q_math.o : $(GDIR)/q_math.c; $(DO_SHLIB_CC) +$(B)/baseq3/game/q_shared.o : $(GDIR)/q_shared.c; $(DO_SHLIB_CC) + + +############################################################################# +## BASEQ3 UI +############################################################################# + +Q3UIOBJ = \ + $(B)/baseq3/ui/bg_misc.o \ + $(B)/baseq3/ui/ui_addbots.o \ + $(B)/baseq3/ui/ui_atoms.o \ + $(B)/baseq3/ui/ui_cdkey.o \ + $(B)/baseq3/ui/ui_cinematics.o \ + $(B)/baseq3/ui/ui_confirm.o \ + $(B)/baseq3/ui/ui_connect.o \ + $(B)/baseq3/ui/ui_controls2.o \ + $(B)/baseq3/ui/ui_credits.o \ + $(B)/baseq3/ui/ui_demo2.o \ + $(B)/baseq3/ui/ui_display.o \ + $(B)/baseq3/ui/ui_gameinfo.o \ + $(B)/baseq3/ui/ui_ingame.o \ + $(B)/baseq3/ui/ui_loadconfig.o \ + $(B)/baseq3/ui/ui_main.o \ + $(B)/baseq3/ui/ui_menu.o \ + $(B)/baseq3/ui/ui_mfield.o \ + $(B)/baseq3/ui/ui_mods.o \ + $(B)/baseq3/ui/ui_network.o \ + $(B)/baseq3/ui/ui_options.o \ + $(B)/baseq3/ui/ui_playermodel.o \ + $(B)/baseq3/ui/ui_players.o \ + $(B)/baseq3/ui/ui_playersettings.o \ + $(B)/baseq3/ui/ui_preferences.o \ + $(B)/baseq3/ui/ui_qmenu.o \ + $(B)/baseq3/ui/ui_removebots.o \ + $(B)/baseq3/ui/ui_saveconfig.o \ + $(B)/baseq3/ui/ui_serverinfo.o \ + $(B)/baseq3/ui/ui_servers2.o \ + $(B)/baseq3/ui/ui_setup.o \ + $(B)/baseq3/ui/ui_sound.o \ + $(B)/baseq3/ui/ui_sparena.o \ + $(B)/baseq3/ui/ui_specifyserver.o \ + $(B)/baseq3/ui/ui_splevel.o \ + $(B)/baseq3/ui/ui_sppostgame.o \ + $(B)/baseq3/ui/ui_spskill.o \ + $(B)/baseq3/ui/ui_startserver.o \ + $(B)/baseq3/ui/ui_syscalls.o \ + $(B)/baseq3/ui/ui_team.o \ + $(B)/baseq3/ui/ui_teamorders.o \ + $(B)/baseq3/ui/ui_video.o \ + \ + $(B)/baseq3/ui/q_math.o \ + $(B)/baseq3/ui/q_shared.o + +$(B)/baseq3/ui$(ARCH).$(SHLIBEXT) : $(Q3UIOBJ) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3UIOBJ) + +$(B)/baseq3/ui/bg_misc.o : $(GDIR)/bg_misc.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_addbots.o : $(Q3UIDIR)/ui_addbots.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_atoms.o : $(Q3UIDIR)/ui_atoms.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_cinematics.o : $(Q3UIDIR)/ui_cinematics.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_cdkey.o : $(Q3UIDIR)/ui_cdkey.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_confirm.o : $(Q3UIDIR)/ui_confirm.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_connect.o : $(Q3UIDIR)/ui_connect.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_controls2.o : $(Q3UIDIR)/ui_controls2.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_credits.o : $(Q3UIDIR)/ui_credits.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_demo2.o : $(Q3UIDIR)/ui_demo2.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_display.o : $(Q3UIDIR)/ui_display.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_gameinfo.o : $(Q3UIDIR)/ui_gameinfo.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_ingame.o : $(Q3UIDIR)/ui_ingame.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_loadconfig.o : $(Q3UIDIR)/ui_loadconfig.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_main.o : $(Q3UIDIR)/ui_main.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_menu.o : $(Q3UIDIR)/ui_menu.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_mfield.o : $(Q3UIDIR)/ui_mfield.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_mods.o : $(Q3UIDIR)/ui_mods.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_network.o : $(Q3UIDIR)/ui_network.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_options.o : $(Q3UIDIR)/ui_options.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_playermodel.o : $(Q3UIDIR)/ui_playermodel.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_players.o : $(Q3UIDIR)/ui_players.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_playersettings.o : $(Q3UIDIR)/ui_playersettings.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_preferences.o : $(Q3UIDIR)/ui_preferences.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_qmenu.o : $(Q3UIDIR)/ui_qmenu.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_quit.o : $(Q3UIDIR)/ui_quit.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_removebots.o : $(Q3UIDIR)/ui_removebots.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_saveconfig.o : $(Q3UIDIR)/ui_saveconfig.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_serverinfo.o : $(Q3UIDIR)/ui_serverinfo.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_servers2.o : $(Q3UIDIR)/ui_servers2.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_setup.o : $(Q3UIDIR)/ui_setup.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_sound.o : $(Q3UIDIR)/ui_sound.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_sparena.o : $(Q3UIDIR)/ui_sparena.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_specifyserver.o : $(Q3UIDIR)/ui_specifyserver.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_splevel.o : $(Q3UIDIR)/ui_splevel.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_sppostgame.o : $(Q3UIDIR)/ui_sppostgame.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_spskill.o : $(Q3UIDIR)/ui_spskill.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_startserver.o : $(Q3UIDIR)/ui_startserver.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_team.o : $(Q3UIDIR)/ui_team.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_teamorders.o : $(Q3UIDIR)/ui_teamorders.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_syscalls.o : $(Q3UIDIR)/ui_syscalls.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/ui_video.o : $(Q3UIDIR)/ui_video.c; $(DO_SHLIB_CC) + +# bk001205 - these wre the only SHLIB compiles in 1.17 +$(B)/baseq3/ui/q_math.o : $(GDIR)/q_math.c; $(DO_SHLIB_CC) +$(B)/baseq3/ui/q_shared.o : $(GDIR)/q_shared.c; $(DO_SHLIB_CC) + + +############################################################################# +## Q3 STATIC (DEBUG) BUILD +############################################################################# + +Q3SOBJ = \ + $(B)/q3static/cl_cgame.o \ + $(B)/q3static/cl_cin.o \ + $(B)/q3static/cl_console.o \ + $(B)/q3static/cl_input.o \ + $(B)/q3static/cl_keys.o \ + $(B)/q3static/cl_main.o \ + $(B)/q3static/cl_net_chan.o \ + $(B)/q3static/cl_parse.o \ + $(B)/q3static/cl_scrn.o \ + $(B)/q3static/cl_ui.o \ + \ + $(B)/q3static/cm_load.o \ + $(B)/q3static/cm_patch.o \ + $(B)/q3static/cm_polylib.o \ + $(B)/q3static/cm_test.o \ + $(B)/q3static/cm_trace.o \ + \ + $(B)/q3static/cmd.o \ + $(B)/q3static/common.o \ + $(B)/q3static/cvar.o \ + $(B)/q3static/files.o \ + $(B)/q3static/md4.o \ + $(B)/q3static/msg.o \ + $(B)/q3static/net_chan.o \ + \ + $(B)/q3static/snd_adpcm.o \ + $(B)/q3static/snd_dma.o \ + $(B)/q3static/snd_mem.o \ + $(B)/q3static/snd_mix.o \ + $(B)/q3static/snd_wavelet.o \ + \ + $(B)/q3static/sv_bot.o \ + $(B)/q3static/sv_ccmds.o \ + $(B)/q3static/sv_client.o \ + $(B)/q3static/sv_game.o \ + $(B)/q3static/sv_init.o \ + $(B)/q3static/sv_main.o \ + $(B)/q3static/sv_net_chan.o \ + $(B)/q3static/sv_snapshot.o \ + $(B)/q3static/sv_world.o \ + \ + $(B)/q3static/unzip.o \ + $(B)/q3static/vm.o \ + $(B)/q3static/vm_interpreted.o \ + \ + $(B)/q3static/be_aas_bspq3.o \ + $(B)/q3static/be_aas_cluster.o \ + $(B)/q3static/be_aas_debug.o \ + $(B)/q3static/be_aas_entity.o \ + $(B)/q3static/be_aas_file.o \ + $(B)/q3static/be_aas_main.o \ + $(B)/q3static/be_aas_move.o \ + $(B)/q3static/be_aas_optimize.o \ + $(B)/q3static/be_aas_reach.o \ + $(B)/q3static/be_aas_route.o \ + $(B)/q3static/be_aas_routealt.o \ + $(B)/q3static/be_aas_sample.o \ + $(B)/q3static/be_ai_char.o \ + $(B)/q3static/be_ai_chat.o \ + $(B)/q3static/be_ai_gen.o \ + $(B)/q3static/be_ai_goal.o \ + $(B)/q3static/be_ai_move.o \ + $(B)/q3static/be_ai_weap.o \ + $(B)/q3static/be_ai_weight.o \ + $(B)/q3static/be_ea.o \ + $(B)/q3static/be_interface.o \ + $(B)/q3static/l_crc.o \ + $(B)/q3static/l_libvar.o \ + $(B)/q3static/l_log.o \ + $(B)/q3static/l_memory.o \ + $(B)/q3static/l_precomp.o \ + $(B)/q3static/l_script.o \ + $(B)/q3static/l_struct.o \ + \ + $(B)/q3static/jcapimin.o \ + $(B)/q3static/jchuff.o \ + $(B)/q3static/jcinit.o \ + $(B)/q3static/jccoefct.o \ + $(B)/q3static/jccolor.o \ + $(B)/q3static/jfdctflt.o \ + $(B)/q3static/jcdctmgr.o \ + $(B)/q3static/jcphuff.o \ + $(B)/q3static/jcmainct.o \ + $(B)/q3static/jcmarker.o \ + $(B)/q3static/jcmaster.o \ + $(B)/q3static/jcomapi.o \ + $(B)/q3static/jcparam.o \ + $(B)/q3static/jcprepct.o \ + $(B)/q3static/jcsample.o \ + $(B)/q3static/jdapimin.o \ + $(B)/q3static/jdapistd.o \ + $(B)/q3static/jdatasrc.o \ + $(B)/q3static/jdcoefct.o \ + $(B)/q3static/jdcolor.o \ + $(B)/q3static/jddctmgr.o \ + $(B)/q3static/jdhuff.o \ + $(B)/q3static/jdinput.o \ + $(B)/q3static/jdmainct.o \ + $(B)/q3static/jdmarker.o \ + $(B)/q3static/jdmaster.o \ + $(B)/q3static/jdpostct.o \ + $(B)/q3static/jdsample.o \ + $(B)/q3static/jdtrans.o \ + $(B)/q3static/jerror.o \ + $(B)/q3static/jidctflt.o \ + $(B)/q3static/jmemmgr.o \ + $(B)/q3static/jmemnobs.o \ + $(B)/q3static/jutils.o \ + \ + $(B)/q3static/tr_animation.o \ + $(B)/q3static/tr_backend.o \ + $(B)/q3static/tr_bsp.o \ + $(B)/q3static/tr_cmds.o \ + $(B)/q3static/tr_curve.o \ + $(B)/q3static/tr_flares.o \ + $(B)/q3static/tr_font.o \ + $(B)/q3static/tr_image.o \ + $(B)/q3static/tr_init.o \ + $(B)/q3static/tr_light.o \ + $(B)/q3static/tr_main.o \ + $(B)/q3static/tr_marks.o \ + $(B)/q3static/tr_mesh.o \ + $(B)/q3static/tr_model.o \ + $(B)/q3static/tr_noise.o \ + $(B)/q3static/tr_scene.o \ + $(B)/q3static/tr_shade.o \ + $(B)/q3static/tr_shade_calc.o \ + $(B)/q3static/tr_shader.o \ + $(B)/q3static/tr_shadows.o \ + $(B)/q3static/tr_sky.o \ + $(B)/q3static/tr_surface.o \ + $(B)/q3static/tr_world.o \ + \ + $(B)/q3static/unix_main.o \ + $(B)/q3static/unix_net.o \ + $(B)/q3static/unix_shared.o \ + \ + $(B)/q3static/ahoptim.o \ + $(B)/q3static/autohint.o \ + $(B)/q3static/ftbase.o \ + $(B)/q3static/ftdebug.o \ + $(B)/q3static/ftglyph.o \ + $(B)/q3static/ftinit.o \ + $(B)/q3static/ftmm.o \ + $(B)/q3static/ftsystem.o \ + $(B)/q3static/raster1.o \ + $(B)/q3static/sfnt.o \ + $(B)/q3static/sfobjs.o \ + $(B)/q3static/smooth.o \ + $(B)/q3static/truetype.o \ + \ + $(B)/q3static/linux_qgl.o \ + $(B)/q3static/linux_glimp.o \ + $(B)/q3static/linux_joystick.o \ + $(B)/q3static/linux_snd.o \ + $(B)/q3static/snd_mixa.o \ + $(B)/q3static/matha.o + +ifeq ($(ARCH),i386) + Q3SOBJ += $(B)/q3static/vm_x86.o +endif + +ifeq ($(ARCH),ppc) + ifeq ($(DLL_ONLY),false) + Q3SOBJ += $(B)/q3static/vm_ppc.o + endif +endif + + +$(B)/q3static/cl_cgame.o : $(CDIR)/cl_cgame.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_cin.o : $(CDIR)/cl_cin.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_console.o : $(CDIR)/cl_console.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_input.o : $(CDIR)/cl_input.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_keys.o : $(CDIR)/cl_keys.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_main.o : $(CDIR)/cl_main.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_net_chan.o : $(CDIR)/cl_net_chan.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_parse.o : $(CDIR)/cl_parse.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_scrn.o : $(CDIR)/cl_scrn.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cl_ui.o : $(CDIR)/cl_ui.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/snd_adpcm.o : $(CDIR)/snd_adpcm.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/snd_dma.o : $(CDIR)/snd_dma.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/snd_mem.o : $(CDIR)/snd_mem.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/snd_mix.o : $(CDIR)/snd_mix.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/snd_wavelet.o : $(CDIR)/snd_wavelet.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_bot.o : $(SDIR)/sv_bot.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_client.o : $(SDIR)/sv_client.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_ccmds.o : $(SDIR)/sv_ccmds.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_game.o : $(SDIR)/sv_game.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_init.o : $(SDIR)/sv_init.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_main.o : $(SDIR)/sv_main.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_net_chan.o : $(SDIR)/sv_net_chan.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_snapshot.o : $(SDIR)/sv_snapshot.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sv_world.o : $(SDIR)/sv_world.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cm_trace.o : $(CMDIR)/cm_trace.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cm_load.o : $(CMDIR)/cm_load.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cm_test.o : $(CMDIR)/cm_test.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cm_patch.o : $(CMDIR)/cm_patch.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cm_polylib.o : $(CMDIR)/cm_polylib.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cmd.o : $(CMDIR)/cmd.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/common.o : $(CMDIR)/common.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cvar.o : $(CMDIR)/cvar.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/files.o : $(CMDIR)/files.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/md4.o : $(CMDIR)/md4.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/msg.o : $(CMDIR)/msg.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/net_chan.o : $(CMDIR)/net_chan.c; $(DO_CC) -DQ3_STATIC + +$(B)/q3static/be_aas_bspq3.o : $(BLIBDIR)/be_aas_bspq3.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_cluster.o : $(BLIBDIR)/be_aas_cluster.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_debug.o : $(BLIBDIR)/be_aas_debug.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_entity.o : $(BLIBDIR)/be_aas_entity.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_file.o : $(BLIBDIR)/be_aas_file.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_main.o : $(BLIBDIR)/be_aas_main.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_move.o : $(BLIBDIR)/be_aas_move.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_optimize.o : $(BLIBDIR)/be_aas_optimize.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_reach.o : $(BLIBDIR)/be_aas_reach.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_route.o : $(BLIBDIR)/be_aas_route.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_routealt.o : $(BLIBDIR)/be_aas_routealt.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_aas_sample.o : $(BLIBDIR)/be_aas_sample.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_ai_char.o : $(BLIBDIR)/be_ai_char.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_ai_chat.o : $(BLIBDIR)/be_ai_chat.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_ai_gen.o : $(BLIBDIR)/be_ai_gen.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_ai_goal.o : $(BLIBDIR)/be_ai_goal.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_ai_move.o : $(BLIBDIR)/be_ai_move.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_ai_weap.o : $(BLIBDIR)/be_ai_weap.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_ai_weight.o : $(BLIBDIR)/be_ai_weight.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_ea.o : $(BLIBDIR)/be_ea.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/be_interface.o : $(BLIBDIR)/be_interface.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/l_crc.o : $(BLIBDIR)/l_crc.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/l_libvar.o : $(BLIBDIR)/l_libvar.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/l_log.o : $(BLIBDIR)/l_log.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/l_memory.o : $(BLIBDIR)/l_memory.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/l_precomp.o : $(BLIBDIR)/l_precomp.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/l_script.o : $(BLIBDIR)/l_script.c; $(DO_BOT_CC) -DQ3_STATIC +$(B)/q3static/l_struct.o : $(BLIBDIR)/l_struct.c; $(DO_BOT_CC) -DQ3_STATIC + +$(B)/q3static/jcapimin.o : $(JPDIR)/jcapimin.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jchuff.o : $(JPDIR)/jchuff.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcinit.o : $(JPDIR)/jcinit.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jccoefct.o : $(JPDIR)/jccoefct.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jccolor.o : $(JPDIR)/jccolor.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jfdctflt.o : $(JPDIR)/jfdctflt.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcdctmgr.o : $(JPDIR)/jcdctmgr.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcmainct.o : $(JPDIR)/jcmainct.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcmarker.o : $(JPDIR)/jcmarker.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcmaster.o : $(JPDIR)/jcmaster.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcomapi.o : $(JPDIR)/jcomapi.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcparam.o : $(JPDIR)/jcparam.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcprepct.o : $(JPDIR)/jcprepct.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcsample.o : $(JPDIR)/jcsample.c; $(DO_CC) -DQ3_STATIC + +$(B)/q3static/jdapimin.o : $(JPDIR)/jdapimin.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdapistd.o : $(JPDIR)/jdapistd.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdatasrc.o : $(JPDIR)/jdatasrc.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdcoefct.o : $(JPDIR)/jdcoefct.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdcolor.o : $(JPDIR)/jdcolor.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jcphuff.o : $(JPDIR)/jcphuff.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jddctmgr.o : $(JPDIR)/jddctmgr.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdhuff.o : $(JPDIR)/jdhuff.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdinput.o : $(JPDIR)/jdinput.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdmainct.o : $(JPDIR)/jdmainct.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdmarker.o : $(JPDIR)/jdmarker.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdmaster.o : $(JPDIR)/jdmaster.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdpostct.o : $(JPDIR)/jdpostct.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdsample.o : $(JPDIR)/jdsample.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jdtrans.o : $(JPDIR)/jdtrans.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jerror.o : $(JPDIR)/jerror.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jidctflt.o : $(JPDIR)/jidctflt.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jmemmgr.o : $(JPDIR)/jmemmgr.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jmemnobs.o : $(JPDIR)/jmemnobs.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/jutils.o : $(JPDIR)/jutils.c; $(DO_CC) -DQ3_STATIC + +$(B)/q3static/tr_bsp.o : $(RDIR)/tr_bsp.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_animation.o : $(RDIR)/tr_animation.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_backend.o : $(RDIR)/tr_backend.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_cmds.o : $(RDIR)/tr_cmds.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_curve.o : $(RDIR)/tr_curve.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_flares.o : $(RDIR)/tr_flares.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_font.o : $(RDIR)/tr_font.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_image.o : $(RDIR)/tr_image.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_init.o : $(RDIR)/tr_init.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_light.o : $(RDIR)/tr_light.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_main.o : $(RDIR)/tr_main.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_marks.o : $(RDIR)/tr_marks.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_mesh.o : $(RDIR)/tr_mesh.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_model.o : $(RDIR)/tr_model.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_noise.o : $(RDIR)/tr_noise.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_scene.o : $(RDIR)/tr_scene.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_shade.o : $(RDIR)/tr_shade.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_shader.o : $(RDIR)/tr_shader.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_shade_calc.o : $(RDIR)/tr_shade_calc.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_shadows.o : $(RDIR)/tr_shadows.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_sky.o : $(RDIR)/tr_sky.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_smp.o : $(RDIR)/tr_smp.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_stripify.o : $(RDIR)/tr_stripify.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_subdivide.o : $(RDIR)/tr_subdivide.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_surface.o : $(RDIR)/tr_surface.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/tr_world.o : $(RDIR)/tr_world.c; $(DO_CC) -DQ3_STATIC + +$(B)/q3static/unix_qgl.o : $(UDIR)/unix_qgl.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/unix_main.o : $(UDIR)/unix_main.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/unix_net.o : $(UDIR)/unix_net.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/unix_shared.o : $(UDIR)/unix_shared.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/linux_glimp.o : $(UDIR)/linux_glimp.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/linux_joystick.o : $(UDIR)/linux_joystick.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/linux_qgl.o : $(UDIR)/linux_qgl.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/linux_input.o : $(UDIR)/linux_input.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/linux_snd.o : $(UDIR)/linux_snd.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/snd_mixa.o : $(UDIR)/snd_mixa.s; $(DO_AS) +$(B)/q3static/matha.o : $(UDIR)/matha.s; $(DO_AS) +$(B)/q3static/unzip.o : $(CMDIR)/unzip.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/vm.o : $(CMDIR)/vm.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/vm_interpreted.o : $(CMDIR)/vm_interpreted.c; $(DO_CC) -DQ3_STATIC + +ifeq ($(ARCH),i386) + $(B)/q3static/vm_x86.o : $(CMDIR)/vm_x86.c; $(DO_CC) -DQ3_STATIC +endif + +ifeq ($(ARCH),ppc) +ifeq ($(DLL_ONLY),false) +$(B)/q3static/vm_ppc.o : $(CMDIR)/vm_ppc.c; $(DO_CC) -DQ3_STATIC +endif +endif + +$(B)/q3static/ahoptim.o : $(FTDIR)/ahoptim.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/autohint.o : $(FTDIR)/autohint.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ftbase.o : $(FTDIR)/ftbase.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ftdebug.o : $(FTDIR)/ftdebug.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ftglyph.o : $(FTDIR)/ftglyph.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ftinit.o : $(FTDIR)/ftinit.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ftmm.o : $(FTDIR)/ftmm.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ftsystem.o : $(FTDIR)/ftsystem.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/raster1.o : $(FTDIR)/raster1.c; $(DO_CC) -DQ3_STATIC -DFT_FLAT_COMPILE +$(B)/q3static/sfnt.o : $(FTDIR)/sfnt.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/sfobjs.o : $(FTDIR)/sfobjs.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/smooth.o : $(FTDIR)/smooth.c; $(DO_CC) -DQ3_STATIC -DFT_FLAT_COMPILE +$(B)/q3static/truetype.o : $(FTDIR)/truetype.c; $(DO_CC) -DQ3_STATIC + +## add BASEQ3 CGAME +Q3SOBJ += \ + $(B)/q3static/cg_consolecmds.o \ + $(B)/q3static/cg_draw.o \ + $(B)/q3static/cg_drawtools.o \ + $(B)/q3static/cg_effects.o \ + $(B)/q3static/cg_ents.o \ + $(B)/q3static/cg_event.o \ + $(B)/q3static/cg_info.o \ + $(B)/q3static/cg_localents.o \ + $(B)/q3static/cg_main.o \ + $(B)/q3static/cg_marks.o \ + $(B)/q3static/cg_players.o \ + $(B)/q3static/cg_playerstate.o \ + $(B)/q3static/cg_predict.o \ + $(B)/q3static/cg_scoreboard.o \ + $(B)/q3static/cg_servercmds.o \ + $(B)/q3static/cg_snapshot.o \ + $(B)/q3static/cg_syscalls.o \ + $(B)/q3static/cg_view.o \ + $(B)/q3static/cg_weapons.o + +$(B)/q3static/cg_consolecmds.o : $(CGDIR)/cg_consolecmds.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_draw.o : $(CGDIR)/cg_draw.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_drawtools.o : $(CGDIR)/cg_drawtools.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_effects.o : $(CGDIR)/cg_effects.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_ents.o : $(CGDIR)/cg_ents.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_event.o : $(CGDIR)/cg_event.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_info.o : $(CGDIR)/cg_info.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_localents.o : $(CGDIR)/cg_localents.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_main.o : $(CGDIR)/cg_main.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_marks.o : $(CGDIR)/cg_marks.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_players.o : $(CGDIR)/cg_players.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_playerstate.o : $(CGDIR)/cg_playerstate.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_predict.o : $(CGDIR)/cg_predict.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_scoreboard.o : $(CGDIR)/cg_scoreboard.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_servercmds.o : $(CGDIR)/cg_servercmds.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_snapshot.o : $(CGDIR)/cg_snapshot.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_syscalls.o : $(CGDIR)/cg_syscalls.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_view.o : $(CGDIR)/cg_view.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/cg_weapons.o : $(CGDIR)/cg_weapons.c; $(DO_CC) -DQ3_STATIC + +## add BASEQ3 GAME +Q3SOBJ += \ + $(B)/q3static/ai_chat.o \ + $(B)/q3static/ai_cmd.o \ + $(B)/q3static/ai_dmnet.o \ + $(B)/q3static/ai_dmq3.o \ + $(B)/q3static/ai_main.o \ + $(B)/q3static/ai_team.o \ + $(B)/q3static/ai_vcmd.o \ + $(B)/q3static/g_active.o \ + $(B)/q3static/g_arenas.o \ + $(B)/q3static/g_bot.o \ + $(B)/q3static/g_client.o \ + $(B)/q3static/g_cmds.o \ + $(B)/q3static/g_combat.o \ + $(B)/q3static/g_items.o \ + $(B)/q3static/g_main.o \ + $(B)/q3static/g_mem.o \ + $(B)/q3static/g_misc.o \ + $(B)/q3static/g_missile.o \ + $(B)/q3static/g_mover.o \ + $(B)/q3static/g_session.o \ + $(B)/q3static/g_spawn.o \ + $(B)/q3static/g_svcmds.o \ + $(B)/q3static/g_target.o \ + $(B)/q3static/g_team.o \ + $(B)/q3static/g_trigger.o \ + $(B)/q3static/g_utils.o \ + $(B)/q3static/g_weapon.o \ + \ + $(B)/q3static/g_syscalls.o + +$(B)/q3static/ai_chat.o : $(GDIR)/ai_chat.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ai_cmd.o : $(GDIR)/ai_cmd.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ai_dmnet.o : $(GDIR)/ai_dmnet.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ai_dmq3.o : $(GDIR)/ai_dmq3.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ai_main.o : $(GDIR)/ai_main.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ai_team.o : $(GDIR)/ai_team.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ai_vcmd.o : $(GDIR)/ai_vcmd.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_active.o : $(GDIR)/g_active.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_arenas.o : $(GDIR)/g_arenas.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_bot.o : $(GDIR)/g_bot.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_client.o : $(GDIR)/g_client.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_cmds.o : $(GDIR)/g_cmds.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_combat.o : $(GDIR)/g_combat.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_items.o : $(GDIR)/g_items.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_main.o : $(GDIR)/g_main.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_mem.o : $(GDIR)/g_mem.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_misc.o : $(GDIR)/g_misc.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_missile.o : $(GDIR)/g_missile.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_mover.o : $(GDIR)/g_mover.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_session.o : $(GDIR)/g_session.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_spawn.o : $(GDIR)/g_spawn.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_svcmds.o : $(GDIR)/g_svcmds.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_syscalls.o : $(GDIR)/g_syscalls.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_target.o : $(GDIR)/g_target.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_team.o : $(GDIR)/g_team.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_trigger.o : $(GDIR)/g_trigger.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_utils.o : $(GDIR)/g_utils.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/g_weapon.o : $(GDIR)/g_weapon.c; $(DO_CC) -DQ3_STATIC + +## add BASEQ3 UI +Q3SOBJ += \ + $(B)/q3static/ui_addbots.o \ + $(B)/q3static/ui_atoms.o \ + $(B)/q3static/ui_cdkey.o \ + $(B)/q3static/ui_cinematics.o \ + $(B)/q3static/ui_confirm.o \ + $(B)/q3static/ui_connect.o \ + $(B)/q3static/ui_controls2.o \ + $(B)/q3static/ui_credits.o \ + $(B)/q3static/ui_demo2.o \ + $(B)/q3static/ui_display.o \ + $(B)/q3static/ui_gameinfo.o \ + $(B)/q3static/ui_ingame.o \ + $(B)/q3static/ui_loadconfig.o \ + $(B)/q3static/ui_main.o \ + $(B)/q3static/ui_menu.o \ + $(B)/q3static/ui_mfield.o \ + $(B)/q3static/ui_mods.o \ + $(B)/q3static/ui_network.o \ + $(B)/q3static/ui_options.o \ + $(B)/q3static/ui_playermodel.o \ + $(B)/q3static/ui_players.o \ + $(B)/q3static/ui_playersettings.o \ + $(B)/q3static/ui_preferences.o \ + $(B)/q3static/ui_qmenu.o \ + $(B)/q3static/ui_removebots.o \ + $(B)/q3static/ui_saveconfig.o \ + $(B)/q3static/ui_serverinfo.o \ + $(B)/q3static/ui_servers2.o \ + $(B)/q3static/ui_setup.o \ + $(B)/q3static/ui_sound.o \ + $(B)/q3static/ui_sparena.o \ + $(B)/q3static/ui_specifyserver.o \ + $(B)/q3static/ui_splevel.o \ + $(B)/q3static/ui_sppostgame.o \ + $(B)/q3static/ui_spskill.o \ + $(B)/q3static/ui_startserver.o \ + $(B)/q3static/ui_team.o \ + $(B)/q3static/ui_teamorders.o \ + $(B)/q3static/ui_video.o \ + \ + $(B)/q3static/ui_syscalls.o + +$(B)/q3static/ui_addbots.o : $(Q3UIDIR)/ui_addbots.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_atoms.o : $(Q3UIDIR)/ui_atoms.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_cinematics.o : $(Q3UIDIR)/ui_cinematics.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_cdkey.o : $(Q3UIDIR)/ui_cdkey.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_confirm.o : $(Q3UIDIR)/ui_confirm.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_connect.o : $(Q3UIDIR)/ui_connect.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_controls2.o : $(Q3UIDIR)/ui_controls2.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_credits.o : $(Q3UIDIR)/ui_credits.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_demo2.o : $(Q3UIDIR)/ui_demo2.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_display.o : $(Q3UIDIR)/ui_display.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_gameinfo.o : $(Q3UIDIR)/ui_gameinfo.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_ingame.o : $(Q3UIDIR)/ui_ingame.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_loadconfig.o : $(Q3UIDIR)/ui_loadconfig.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_main.o : $(Q3UIDIR)/ui_main.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_menu.o : $(Q3UIDIR)/ui_menu.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_mfield.o : $(Q3UIDIR)/ui_mfield.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_mods.o : $(Q3UIDIR)/ui_mods.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_network.o : $(Q3UIDIR)/ui_network.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_options.o : $(Q3UIDIR)/ui_options.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_playermodel.o : $(Q3UIDIR)/ui_playermodel.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_players.o : $(Q3UIDIR)/ui_players.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_playersettings.o : $(Q3UIDIR)/ui_playersettings.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_preferences.o : $(Q3UIDIR)/ui_preferences.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_qmenu.o : $(Q3UIDIR)/ui_qmenu.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_quit.o : $(Q3UIDIR)/ui_quit.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_removebots.o : $(Q3UIDIR)/ui_removebots.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_saveconfig.o : $(Q3UIDIR)/ui_saveconfig.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_serverinfo.o : $(Q3UIDIR)/ui_serverinfo.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_servers2.o : $(Q3UIDIR)/ui_servers2.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_setup.o : $(Q3UIDIR)/ui_setup.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_sound.o : $(Q3UIDIR)/ui_sound.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_sparena.o : $(Q3UIDIR)/ui_sparena.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_specifyserver.o : $(Q3UIDIR)/ui_specifyserver.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_splevel.o : $(Q3UIDIR)/ui_splevel.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_sppostgame.o : $(Q3UIDIR)/ui_sppostgame.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_spskill.o : $(Q3UIDIR)/ui_spskill.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_startserver.o : $(Q3UIDIR)/ui_startserver.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_team.o : $(Q3UIDIR)/ui_team.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_teamorders.o : $(Q3UIDIR)/ui_teamorders.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_syscalls.o : $(Q3UIDIR)/ui_syscalls.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/ui_video.o : $(Q3UIDIR)/ui_video.c; $(DO_CC) -DQ3_STATIC + + +## add shared files +Q3SOBJ += \ + $(B)/q3static/bg_misc.o \ + $(B)/q3static/bg_pmove.o \ + $(B)/q3static/bg_slidemove.o \ + $(B)/q3static/q_math.o \ + $(B)/q3static/q_shared.o + +## shared files +$(B)/q3static/q_math.o : $(GDIR)/q_math.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/q_shared.o : $(GDIR)/q_shared.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/bg_misc.o : $(GDIR)/bg_misc.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/bg_pmove.o : $(GDIR)/bg_pmove.c; $(DO_CC) -DQ3_STATIC +$(B)/q3static/bg_slidemove.o : $(GDIR)/bg_slidemove.c; $(DO_CC) -DQ3_STATIC + + + +$(B)/$(PLATFORM)q3static : $(Q3SOBJ) + $(CC) $(CFLAGS) -o $@ $(Q3SOBJ) $(GLLDFLAGS) $(LDFLAGS) + + +############################################################################# +# RPM +############################################################################# + +TMPDIR=/var/tmp +TARDIR=$(TMPDIR)/$(BUILD_NAME) +TARFILE = $(BUILD_NAME)-$(VERSION)-$(RPM_RELEASE).$(ARCH).tar + +tar: + if [ ! -d archives ];then mkdir archives;chmod 755 archives;fi + $(MAKE) copyfiles COPYDIR=$(TARDIR) + cd $(TARDIR); tar cvf $(TARFILE) * && gzip -9 $(TARFILE) + mv $(TARDIR)/$(TARFILE).gz archives/. + 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/$(BUILD_NAME) +RPMDIR = $(TMPDIR)/$(BUILD_NAME)-$(VERSION) +DESTDIR= $(RPMDIR)/$(INSTALLDIR) + +rpm: $(BUILD_NAME).spec + touch $(RPMROOT)/SOURCES/$(BUILD_NAME)-$(VERSION).tar.gz + if [ ! -d archives ];then mkdir archives;fi + $(MAKE) copyfiles COPYDIR=$(DESTDIR) + cp $(UDIR)/quake3.gif $(RPMROOT)/SOURCES/. + cp $(BUILD_NAME).spec $(RPMROOT)/SPECS/. + cd $(RPMROOT)/SPECS; $(RPM) $(RPMFLAGS) $(BUILD_NAME).spec + rm -rf $(RPMDIR) + mv $(RPMROOT)/RPMS/$(RPMARCH)/$(BUILD_NAME)-$(VERSION)-$(RPM_RELEASE).$(RPMARCH).rpm archives/$(BUILD_NAME)-$(VERSION)-$(RPM_RELEASE).$(RPMARCH).rpm + +copyfiles: + -mkdirhier $(COPYDIR) + cp $(BR)/linuxquake3 $(COPYDIR)/quake3.x86 + strip $(COPYDIR)/quake3.x86 + chmod 755 $(COPYDIR)/quake3.x86 + cp $(BR)/linuxq3ded $(COPYDIR)/q3ded + strip $(COPYDIR)/q3ded + chmod 755 $(COPYDIR)/q3ded + cp $(BDIR)/libMesaVoodooGL.so.3.2 $(COPYDIR)/. + chmod 755 $(COPYDIR)/libMesaVoodooGL.so.3.2 + ( cd $(COPYDIR); ln -s libMesaVoodooGL.so.3.2 libMesaVoodooGL.so ) + cp $(BDIR)/Quake_III_Arena_FAQ.html $(COPYDIR)/. + chmod 644 $(COPYDIR)/Quake_III_Arena_FAQ.html + mkdir $(COPYDIR)/baseq3 + cp $(BASEQ3_DIR)/pak2.pk3 $(COPYDIR)/baseq3/. + chmod 644 $(COPYDIR)/baseq3/pak2.pk3 + +$(BUILD_NAME).spec : $(UDIR)/$(BUILD_NAME).spec.sh Makefile + sh $< $(VERSION) $(RPM_RELEASE) $(ARCH) $(INSTALLDIR) > $@ + +############################################################################# +# MISC +############################################################################# + +# TTimo: FIXME: doesn't clean the binary and .so + +clean:clean-debug clean-release + +clean2: clean-bins + rm -f $(Q3OBJ) $(Q3POBJ) $(Q3POBJ_SMP) $(Q3DOBJ) $(MPGOBJ) $(Q3GOBJ) $(Q3CGOBJ) $(MPCGOBJ) $(Q3UIOBJ) $(MPUIOBJ) + rm -f $(CGDIR)/vm/*.asm + rm -f $(GDIR)/vm/*.asm + rm -f $(UIDIR)/vm/*.asm + rm -f $(Q3UIDIR)/vm/*.asm + +# TTimo: linuxq3ded linuxquake3 linuxquake3-smp .. hardcoded the names .. maybe not such a good thing +# FIXME: also, removing the *.so is crappy .. I just want to avoid rm -rf debugi386-glibc to save the symlinks to pk3's for testing +clean-bins: + if [ -d $(B) ];then (find $(B) -name '*.so' -exec rm {} \;)fi + rm -f $(B)/linuxq3ded + rm -f $(B)/linuxquake3 + rm -f $(B)/linuxquake3-smp + rm -f $(B)/baseq3/vm/cgame.qvm + rm -f $(B)/baseq3/vm/ui.qvm + rm -f $(B)/baseq3/vm/qagame.qvm + rm -f $(B)/missionpack/vm/cgame.qvm + rm -f $(B)/missionpack/vm/qagame.qvm + rm -f $(B)/missionpack/vm/ui.qvm + +clean-debug: + $(MAKE) clean2 B=$(BD) CFLAGS="$(DEBUG_CFLAGS)" + +clean-release: + $(MAKE) clean2 B=$(BR) CFLAGS="$(DEBUG_CFLAGS)" + +# +# TTimo: the make tar and make rpm don't really fit my needs +# writing custom hack + +DATADIR = /home/timo/Id/Q3SetupMedia/quake3 + +setup: release $(B)/linuxq3a-$(VERSION).tar.gz + +$(B)/linuxq3a-$(VERSION).tar.gz: + rm -rf setup + mkdir setup + cp -R $(DATADIR) setup + cp release$(ARCH)-glibc/linuxquake3 setup/quake3/quake3.x86 + strip setup/quake3/quake3.x86 + cp release$(ARCH)-glibc/linuxq3ded setup/quake3/q3ded + strip setup/quake3/q3ded + cd setup; tar cvzf linuxq3a-$(VERSION).tar.gz quake3 +# TTimo: the .so where distributed with 1.27g because of a VM problem that made them break on linux +# cp release$(ARCH)-glibc/baseq3/cgame$(ARCH).so setup/quake3/baseq3 +# cp release$(ARCH)-glibc/baseq3/qagame$(ARCH).so setup/quake3/baseq3 +# cp release$(ARCH)-glibc/baseq3/ui$(ARCH).so setup/quake3/baseq3 +# cp release$(ARCH)-glibc/missionpack/cgame$(ARCH).so setup/quake3/missionpack +# cp release$(ARCH)-glibc/missionpack/qagame$(ARCH).so setup/quake3/missionpack +# cp release$(ARCH)-glibc/missionpack/ui$(ARCH).so setup/quake3/missionpack + +# don't redistribute this one! +setup-debug: debug $(B)/linuxq3a-$(VERSION)-DEBUG.tar.gz + +$(B)/linuxq3a-$(VERSION)-DEBUG.tar.gz: + rm -rf setup-debug + mkdir setup-debug + cp -R $(DATADIR) setup-debug + cp debug$(ARCH)-glibc/linuxquake3 setup-debug/quake3/quake3.x86 + cp debug$(ARCH)-glibc/linuxq3ded setup-debug/quake3/q3ded + cp debug$(ARCH)-glibc/baseq3/cgame$(ARCH).so setup-debug/quake3/baseq3 + cp debug$(ARCH)-glibc/baseq3/qagame$(ARCH).so setup-debug/quake3/baseq3 + cp debug$(ARCH)-glibc/baseq3/ui$(ARCH).so setup-debug/quake3/baseq3 + cp debug$(ARCH)-glibc/missionpack/cgame$(ARCH).so setup-debug/quake3/missionpack + cp debug$(ARCH)-glibc/missionpack/qagame$(ARCH).so setup-debug/quake3/missionpack + cp debug$(ARCH)-glibc/missionpack/ui$(ARCH).so setup-debug/quake3/missionpack + cd setup-debug; tar cvzf linuxq3a-$(VERSION)-DEBUG.tar.gz quake3 + +## temps +# NOTE: not referenced + +dcp: + cp debug$(ARCH)-glibc/linuxquake3 $(TESTDIR)/quake3-$(VERSION)-debug.$(ARCH) + cp debug$(ARCH)-glibc/linuxq3ded $(TESTDIR)/q3ded-$(VERSION)-debug.$(ARCH) + cp debug$(ARCH)-glibc/baseq3/cgame$(ARCH).so $(TESTDIR)/baseq3/cgame$(ARCH)-debug.so + cp debug$(ARCH)-glibc/baseq3/qagame$(ARCH).so $(TESTDIR)/baseq3/qagame$(ARCH)-debug.so + cp debug$(ARCH)-glibc/baseq3/ui$(ARCH).so $(TESTDIR)/baseq3/ui$(ARCH)-debug.so + cp debug$(ARCH)-glibc/missionpack/cgame$(ARCH).so $(TESTDIR)/missionpack/cgame$(ARCH)-debug.so + cp debug$(ARCH)-glibc/missionpack/qagame$(ARCH).so $(TESTDIR)/missionpack/qagame$(ARCH)-debug.so + cp debug$(ARCH)-glibc/missionpack/ui$(ARCH).so $(TESTDIR)/missionpack/ui$(ARCH)-debug.so + cp debug$(ARCH)-glibc/baseq3/vm/*.qvm $(TESTDIR)/baseq3/vm/ + cp debug$(ARCH)-glibc/missionpack/vm/*.qvm $(TESTDIR)/missionpack/vm/ + +rcp: + cp release$(ARCH)-glibc/linuxquake3 $(TESTDIR)/quake3-$(VERSION).$(ARCH) + cp release$(ARCH)-glibc/linuxq3ded $(TESTDIR)/q3ded-$(VERSION).$(ARCH) + cp release$(ARCH)-glibc/baseq3/cgame$(ARCH).so $(TESTDIR)/baseq3/cgame$(ARCH).so + cp release$(ARCH)-glibc/baseq3/qagame$(ARCH).so $(TESTDIR)/baseq3/qagame$(ARCH).so + cp release$(ARCH)-glibc/baseq3/ui$(ARCH).so $(TESTDIR)/baseq3/ui$(ARCH).so + cp release$(ARCH)-glibc/missionpack/cgame$(ARCH).so $(TESTDIR)/missionpack/cgame$(ARCH).so + cp release$(ARCH)-glibc/missionpack/qagame$(ARCH).so $(TESTDIR)/missionpack/qagame$(ARCH).so + cp release$(ARCH)-glibc/missionpack/ui$(ARCH).so $(TESTDIR)/missionpack/ui$(ARCH).so + cp release$(ARCH)-glibc/baseq3/vm/*.qvm $(TESTDIR)/baseq3/vm/ + cp release$(ARCH)-glibc/missionpack/vm/*.qvm $(TESTDIR)/missionpack/vm/ diff --git a/codemp/unix/snapvector.nasm b/codemp/unix/snapvector.nasm new file mode 100644 index 0000000..705f3d3 --- /dev/null +++ b/codemp/unix/snapvector.nasm @@ -0,0 +1,75 @@ +; +; Sys_SnapVector NASM code (Andrew Henderson) +; See win32/win_shared.c for the Win32 equivalent +; This code is provided to ensure that the +; rounding behavior (and, if necessary, the +; precision) of DLL and QVM code are identical +; e.g. for network-visible operations. +; See ftol.nasm for operations on a single float, +; as used in compiled VM and DLL code that does +; not use this system trap. +; + + +segment .data + +fpucw dd 0 +cw037F dd 0x037F ; Rounding to nearest (even). + +segment .text + +; void Sys_SnapVector( float *v ) +global Sys_SnapVector +Sys_SnapVector: + push eax + push ebp + mov ebp, esp + + fnstcw [fpucw] + mov eax, dword [ebp + 12] + fldcw [cw037F] + fld dword [eax] + fistp dword [eax] + fild dword [eax] + fstp dword [eax] + fld dword [eax + 4] + fistp dword [eax + 4] + fild dword [eax + 4] + fstp dword [eax + 4] + fld dword [eax + 8] + fistp dword [eax + 8] + fild dword [eax + 8] + fstp dword [eax + 8] + fldcw [fpucw] + + pop ebp + pop eax + ret + +; void Sys_SnapVectorCW( float *v, unsigned short int cw ) +global Sys_SnapVectorCW +Sys_SnapVector_cw: + push eax + push ebp + mov ebp, esp + + fnstcw [fpucw] + mov eax, dword [ebp + 12] + fldcw [ebp + 16] + fld dword [eax] + fistp dword [eax] + fild dword [eax] + fstp dword [eax] + fld dword [eax + 4] + fistp dword [eax + 4] + fild dword [eax + 4] + fstp dword [eax + 4] + fld dword [eax + 8] + fistp dword [eax + 8] + fild dword [eax + 8] + fstp dword [eax + 8] + fldcw [fpucw] + + pop ebp + pop eax + ret \ No newline at end of file diff --git a/codemp/unix/unix_main.c b/codemp/unix/unix_main.c new file mode 100644 index 0000000..8d58b9b --- /dev/null +++ b/codemp/unix/unix_main.c @@ -0,0 +1,1164 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __linux__ // rb010123 +#include +#endif +#include + +#ifdef __linux__ +#include // bk001213 - force dumps on divide by zero +#endif + + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" + +#include "linux_local.h" // bk001204 + +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 +// ======================================================================= + +// bk001207 +#define MEM_THRESHOLD 96*1024*1024 +/* +================== +Sys_LowPhysicalMemory() +================== +*/ +qboolean Sys_LowPhysicalMemory() { + //MEMORYSTATUS stat; + //GlobalMemoryStatus (&stat); + //return (stat.dwTotalPhys <= MEM_THRESHOLD) ? qtrue : qfalse; + return qfalse; // bk001207 - FIXME +} + +/* +================== +Sys_FunctionCmp +================== +*/ +int Sys_FunctionCmp(void *f1, void *f2) { + return qtrue; +} + +/* +================== +Sys_FunctionCheckSum +================== +*/ +int Sys_FunctionCheckSum(void *f1) { + return 0; +} + +/* +================== +Sys_MonkeyShouldBeSpanked +================== +*/ +int Sys_MonkeyShouldBeSpanked( void ) { + return 0; +} + +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); + } +} + +// bk010104 - added for abstraction +void Sys_Exit( int ex ) { +#ifdef NDEBUG // regular behavior + // We can't do this + // as long as GL DLL's keep installing with atexit... + //exit(ex); + _exit(ex); +#else + // Give me a backtrace on error exits. + assert( ex == 0 ); + exit(ex); +#endif +} + + +void Sys_Quit (void) { + CL_Shutdown (); + fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); + Sys_Exit(0); +} + +void Sys_Init(void) +{ + Cmd_AddCommand ("in_restart", Sys_In_Restart_f); + +#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" ); +#elif defined __FreeBSD__ + +#if defined __i386__ // FreeBSD + Cvar_Set( "arch", "freebsd i386" ); +#elif defined __alpha__ + Cvar_Set( "arch", "freebsd alpha" ); +#else + Cvar_Set( "arch", "freebsd unknown" ); +#endif // FreeBSD + +#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 + + Cvar_Set( "username", Sys_GetCurrentUser() ); + + 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, "Sys_Error: %s\n", string); + + Sys_Exit( 1 ); // bk010104 - use single exit point. +} + +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) +{ + 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; +} + +/*****************************************************************************/ + +/* +================= +Sys_UnloadDll + +================= +*/ +void Sys_UnloadDll( void *dllHandle ) { + // bk001206 - verbose error reporting + const char* err; // rb010123 - now const + if ( !dllHandle ) { + Com_Printf("Sys_UnloadDll(NULL)\n"); + return; + } + dlclose( dllHandle ); + err = dlerror(); + if ( err != NULL ) + Com_Printf ( "Sys_UnloadGame failed on dlclose: \"%s\"!\n", err ); +} + + +/* +================= +Sys_LoadDll + +Used to load a development dll instead of a virtual machine +================= +*/ +extern char *FS_BuildOSPath( const char *base, const char *game, const char *qpath ); + +void *Sys_LoadDll( const char *name, + int (**entryPoint)(int, ...), + int (*systemcalls)(int, ...) ) +{ + void *libHandle; + void (*dllEntry)( int (*syscallptr)(int, ...) ); + char curpath[MAX_OSPATH]; + char fname[MAX_OSPATH]; + //char loadname[MAX_OSPATH]; + char *basepath; + char *cdpath; + char *gamedir; + char *fn; + const char* err = NULL; // bk001206 // rb0101023 - now const + + // bk001206 - let's have some paranoia + assert( name ); + + getcwd(curpath, sizeof(curpath)); +#if defined __i386__ +#ifndef NDEBUG + snprintf (fname, sizeof(fname), "%si386-debug.so", name); // bk010205 - different DLL name +#else + snprintf (fname, sizeof(fname), "%si386.so", name); +#endif +#elif defined __powerpc__ //rcg010207 - PPC support. + snprintf (fname, sizeof(fname), "%sppc.so", name); +#elif defined __axp__ + snprintf (fname, sizeof(fname), "%saxp.so", name); +#elif defined __mips__ + snprintf (fname, sizeof(fname), "%smips.so", name); +#else +#error Unknown arch +#endif + +// bk001129 - was RTLD_LAZY +#define Q_RTLD RTLD_NOW + +#if 0 // bk010205 - was NDEBUG // bk001129 - FIXME: what is this good for? + // bk001206 - do not have different behavior in builds + Q_strncpyz(loadname, curpath, sizeof(loadname)); + // bk001129 - from cvs1.17 (mkv) + Q_strcat(loadname, sizeof(loadname), "/"); + + Q_strcat(loadname, sizeof(loadname), fname); + Com_Printf( "Sys_LoadDll(%s)... \n", loadname ); + libHandle = dlopen( loadname, Q_RTLD ); + //if ( !libHandle ) { + // bk001206 - report any problem + //Com_Printf( "Sys_LoadDll(%s) failed: \"%s\"\n", loadname, dlerror() ); +#endif // bk010205 - do not load from installdir + + basepath = Cvar_VariableString( "fs_basepath" ); + cdpath = Cvar_VariableString( "fs_cdpath" ); + gamedir = Cvar_VariableString( "fs_game" ); + + fn = FS_BuildOSPath( basepath, gamedir, fname ); + // bk001206 - verbose + Com_Printf( "Sys_LoadDll(%s)... \n", fn ); + + // bk001129 - from cvs1.17 (mkv), was fname not fn + libHandle = dlopen( fn, Q_RTLD ); + + if ( !libHandle ) { + if( cdpath[0] ) { + // bk001206 - report any problem + Com_Printf( "Sys_LoadDll(%s) failed: \"%s\"\n", fn, dlerror() ); + + fn = FS_BuildOSPath( cdpath, gamedir, fname ); + libHandle = dlopen( fn, Q_RTLD ); + if ( !libHandle ) { + // bk001206 - report any problem + Com_Printf( "Sys_LoadDll(%s) failed: \"%s\"\n", fn, dlerror() ); + } + else + Com_Printf ( "Sys_LoadDll(%s): succeeded ...\n", fn ); + } + else + Com_Printf ( "Sys_LoadDll(%s): succeeded ...\n", fn ); + + if ( !libHandle ) { +#ifdef NDEBUG // bk001206 - in debug abort on failure + Com_Error ( ERR_FATAL, "Sys_LoadDll(%s) failed dlopen() completely!\n", name ); +#else + Com_Printf ( "Sys_LoadDll(%s) failed dlopen() completely!\n", name ); +#endif + return NULL; + } + } + // bk001206 - no different behavior + //#ifndef NDEBUG } + //else Com_Printf ( "Sys_LoadDll(%s): succeeded ...\n", loadname ); + //#endif + + dllEntry = (void (*)(int (*)(int,...))) dlsym( libHandle, "dllEntry" ); + *entryPoint = (int(*)(int,...))dlsym( libHandle, "vmMain" ); + if ( !*entryPoint || !dllEntry ) { + err = dlerror(); +#ifdef NDEBUG // bk001206 - in debug abort on failure + Com_Error ( ERR_FATAL, "Sys_LoadDll(%s) failed dlsym(vmMain): \"%s\" !\n", name, err ); +#else + Com_Printf ( "Sys_LoadDll(%s) failed dlsym(vmMain): \"%s\" !\n", name, err ); +#endif + dlclose( libHandle ); + err = dlerror(); + if ( err != NULL ) + Com_Printf ( "Sys_LoadDll(%s) failed dlcose: \"%s\"\n", name, err ); + return NULL; + } + Com_Printf ( "Sys_LoadDll(%s) found **vmMain** at %p \n", name, *entryPoint ); // bk001212 + dllEntry( systemcalls ); + Com_Printf ( "Sys_LoadDll(%s) succeeded!\n", name ); + return libHandle; +} + + +#if 0 // bk010215 - scheduled for full deletion +/*****************************************************************************/ + +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) { + // bk001206 - this code is never used + assert(0); + + 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; // bk001204 - unused + + // bk001206 - this code is never used + assert(0); + + 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) +{ + // bk001206 - this code is never used + assert(0); + 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 + + // bk001206 - this code is never used + assert(0); + + 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) +{ + // bk001206 - this code is never used + assert(0); + if (ui_library) + dlclose (ui_library); + ui_library = NULL; +} + +/* +================= +Sys_GetUIAPI + +Loads the ui dll +================= +*/ +void *Sys_GetUIAPI (void) +{ + void *(*api)(void); + + 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 + + // bk001206 - this code is never used + assert(0); + 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 *(*)(void))dlsym (ui_library, "GetUIAPI"); + if (!api) + { + Com_Error( ERR_FATAL, "dlsym() failed on GetUIAPI" ); + } + + return api(); +} + +/*****************************************************************************/ + +static void *botlib_library; + +/* +================= +Sys_UnloadGame +================= +*/ +void Sys_UnloadBotLib (void) +{ + // bk001206 - this code is never used + assert(0); + if (botlib_library) + dlclose (botlib_library); + botlib_library = NULL; +} + +/* +================= +Sys_GetGameAPI + +Loads the game dll +================= +*/ +void *Sys_GetBotLibAPI (void *parms ) +{ + void *(*GetBotLibAPI) (void *); + char name[MAX_OSPATH]; + char curpath[MAX_OSPATH]; +#ifdef __i386__ + const char *botlibname = "qaboti386.so"; +#elif defined __alpha__ + const char *botlibname = "qabotaxp.so"; +#elif defined __mips__ + const char *botlibname = "qabotmips.so"; +#else +#error Unknown arch +#endif + // bk001129 - this code is never used + assert(0); + + Sys_UnloadBotLib(); + + getcwd(curpath, sizeof(curpath)); + + Com_Printf("------- Loading %s -------\n", botlibname); + + sprintf (name, "%s/%s", curpath, botlibname);\ + // bk001129 - was RTLD_LAZY + botlib_library = dlopen (name, RTLD_NOW ); + if (!botlib_library) + { + Com_Printf ("LoadLibrary (%s)\n",name); + Com_Error( ERR_FATAL, "Couldn't load botlib: %s", dlerror() ); + } + + GetBotLibAPI = (void *)dlsym (botlib_library, "GetBotLibAPI"); + if (!GetBotLibAPI) + { + Sys_UnloadBotLib (); + Com_Error( ERR_FATAL, "dlsym() failed on GetBotLibAPI" ); + } + + // bk001129 - this is a signature mismatch + return GetBotLibAPI (parms); +} + +void *Sys_GetBotAIAPI (void *parms ) { + return NULL; +} + +/*****************************************************************************/ +#endif // bk010215 + + +/* +======================================================================== + +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 { + 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; + + // 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 = FS_Read ( stream.buffer + bufferPoint, readCount, stream.file ); + stream.threadPosition += r; + + if ( r != readCount ) + stream.eof = qtrue; + } + } +} + +/* +=============== +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,TAG_FILESYS,qfalse ); + stream.bufferSize = readAhead; + stream.streamPosition = 0; + stream.threadPosition = 0; + stream.eof = qfalse; +} + +/* +=============== +Sys_EndStreamedFile + +================ +*/ +void Sys_EndStreamedFile( fileHandle_t f ) +{ + if ( f != stream.file ) { + Com_Error( ERR_FATAL, "Sys_EndStreamedFile: wrong file"); + } + + 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; + 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( fileHandle_t f, int offset, int origin ) { + // clear to that point + FS_Seek( f, offset, origin ); + stream.streamPosition = 0; + stream.threadPosition = 0; + stream.eof = qfalse; +} + +#endif + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +// bk000306: upped this from 64 +#define MAX_QUED_EVENTS 256 +#define MASK_QUED_EVENTS ( MAX_QUED_EVENTS - 1 ) + +sysEvent_t eventQue[MAX_QUED_EVENTS]; +// bk000306: initialize +int eventHead = 0; +int eventTail = 0; +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 ]; + + // bk000305 - was missing + 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 ) { + 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 = (char *)Z_Malloc( len,TAG_EVENT,qfalse ); + 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 = (netadr_t *)Z_Malloc( len,TAG_EVENT,qfalse ); + *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; +} + +/*****************************************************************************/ + +qboolean Sys_CheckCD( void ) { + return qtrue; +} + +void Sys_AppActivate (void) +{ +} + +char *Sys_GetClipboardData(void) +{ + return NULL; +} + +void Sys_Print( const char *msg ) +{ + fputs(msg, stderr); +} + + +void Sys_ConfigureFPU() { // bk001213 - divide by zero +#ifdef __linux__ +#ifdef __i386 +#ifndef NDEBUG + // bk0101022 - enable FPE's in debug mode + static int fpu_word = _FPU_DEFAULT & ~(_FPU_MASK_ZM | _FPU_MASK_IM); + int current = 0; + _FPU_GETCW(current); + if ( current!=fpu_word) { +#if 0 + Com_Printf("FPU Control 0x%x (was 0x%x)\n", fpu_word, current ); + _FPU_SETCW( fpu_word ); + _FPU_GETCW( current ); + assert(fpu_word==current); +#endif + } +#else // NDEBUG + static int fpu_word = _FPU_DEFAULT; + _FPU_SETCW( fpu_word ); +#endif // NDEBUG +#endif // __i386 +#endif // __linux +} + + +void Sys_PrintBinVersion( const char* name ) { + char* date = __DATE__; + char* time = __TIME__; + char* sep = "=============================================================="; + fprintf( stdout, "\n\n%s\n", sep ); +#ifdef DEDICATED + fprintf( stdout, "Linux Quake3 Dedicated Server [%s %s]\n", date, time ); +#else + fprintf( stdout, "Linux Quake3 Full Executable [%s %s]\n", date, time ); +#endif + fprintf( stdout, " local install: %s\n", name ); + fprintf( stdout, "%s\n\n", sep ); +} + +void Sys_ParseArgs( int argc, char* argv[] ) { + + if ( argc==2 ) { + if ( (!strcmp( argv[1], "--version" )) + || ( !strcmp( argv[1], "-v" )) ) + { + Sys_PrintBinVersion( argv[0] ); + Sys_Exit(0); + } + } +} + +#include "../client/client.h" +extern clientStatic_t cls; + +int main ( int argc, char* argv[] ) +{ + // int oldtime, newtime; // bk001204 - unused + int len, i; + char *cmdline; + void Sys_SetDefaultCDPath(const char *path); + + // go back to real user for config loads + saved_euid = geteuid(); + seteuid(getuid()); + + Sys_ParseArgs( argc, argv ); // bk010104 - added this for support + + Sys_SetDefaultCDPath(argv[0]); + + // merge the command line, this is kinda silly + for (len = 1, i = 1; i < argc; i++) + len += strlen(argv[i]) + 1; + cmdline = (char *)malloc(len); + *cmdline = 0; + for (i = 1; i < argc; i++) { + if (i > 1) + strcat(cmdline, " "); + strcat(cmdline, argv[i]); + } + + // bk000306 - clear queues + memset( &eventQue[0], 0, MAX_QUED_EVENTS*sizeof(sysEvent_t) ); + memset( &sys_packetReceived[0], 0, MAX_MSGLEN*sizeof(byte) ); + + 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); + } + + while (1) { +#ifdef __linux__ + Sys_ConfigureFPU(); +#endif + Com_Frame (); + } +} diff --git a/codemp/unix/unix_net.c b/codemp/unix/unix_net.c new file mode 100644 index 0000000..df314e2 --- /dev/null +++ b/codemp/unix/unix_net.c @@ -0,0 +1,599 @@ +// unix_net.c + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +#include +#include +#include +#include +#include +#include // bk001204 + +#include +#include +#include +#include + +#ifdef MACOS_X +#import +#import +#import + +#import // for inet_ntoa() +#import // for 'struct sockaddr_dl' +#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; // bk001204 - unused + + 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,(socklen_t*) &fromlen); + + SockadrToNetadr (&from, net_from); + // bk000305: was missing + net_message->readcount = 0; + + 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; + } + + if( adr.type == NA_IPX ) { + return qtrue; + } + + if( adr.type != NA_IP ) { + return qfalse; + } + + // choose which comparison to use based on the class of the address being tested + // any local adresses of a different class than the address being tested will fail based on the first byte +/* + // Class A + if( (adr.ip[0] & 0x80) == 0x00 ) { + for ( i = 0 ; i < numIP ; i++ ) { + if( adr.ip[0] == localIP[i][0] ) { + return qtrue; + } + } + // the RFC1918 class a block will pass the above test + return qfalse; + } + + // Class B + if( (adr.ip[0] & 0xc0) == 0x80 ) { + for ( i = 0 ; i < numIP ; i++ ) { + if( adr.ip[0] == localIP[i][0] && adr.ip[1] == localIP[i][1] ) { + return qtrue; + } + // also check against the RFC1918 class b blocks + if( adr.ip[0] == 172 && localIP[i][0] == 172 && (adr.ip[1] & 0xf0) == 16 && (localIP[i][1] & 0xf0) == 16 ) { + return qtrue; + } + } + return qfalse; + } +*/ + //we only look at class C since ISPs and Universities are using class A but we don't want to consider them on the same LAN. + // Class C + for ( i = 0 ; i < numIP ; i++ ) { + if( adr.ip[0] == localIP[i][0] && adr.ip[1] == localIP[i][1] && adr.ip[2] == localIP[i][2] ) { + return qtrue; + } + // also check against the RFC1918 class c blocks + if( adr.ip[0] == 192 && localIP[i][0] == 192 && adr.ip[1] == 168 && localIP[i][1] == 168 ) { + return qtrue; + } + } + return qfalse; +} + +/* +================== +Sys_ShowIP +================== +*/ +void Sys_ShowIP(void) { + int i; + + for (i = 0; i < numIP; i++) { + Com_Printf( "IP: %i.%i.%i.%i\n", localIP[i][0], localIP[i][1], localIP[i][2], localIP[i][3] ); + } +} + +/* +===================== +NET_GetLocalAddress +===================== +*/ +#ifdef MACOS_X +// Don't do a forward mapping from the hostname of the machine to the IP. The reason is that we might have obtained an IP address from DHCP and there might not be any name registered for the machine. On Mac OS X, the machine name defaults to 'localhost' and NetInfo has 127.0.0.1 listed for this name. Instead, we want to get a list of all the IP network interfaces on the machine. +// This code adapted from OmniNetworking. + +#define IFR_NEXT(ifr) \ + ((struct ifreq *) ((char *) (ifr) + sizeof(*(ifr)) + \ + MAX(0, (int) (ifr)->ifr_addr.sa_len - (int) sizeof((ifr)->ifr_addr)))) + +void NET_GetLocalAddress( void ) { + struct ifreq requestBuffer[MAX_IPS], *linkInterface, *inetInterface; + struct ifconf ifc; + struct ifreq ifr; + struct sockaddr_dl *sdl; + int interfaceSocket; + int family; + + //Com_Printf("NET_GetLocalAddress: Querying for network interfaces\n"); + + // Set this early so we can just return if there is an error + numIP = 0; + + ifc.ifc_len = sizeof(requestBuffer); + ifc.ifc_buf = (caddr_t)requestBuffer; + + // Since we get at this info via an ioctl, we need a temporary little socket. This will only get AF_INET interfaces, but we probably don't care about anything else. If we do end up caring later, we should add a ONAddressFamily and at a -interfaces method to it. + family = AF_INET; + if ((interfaceSocket = socket(family, SOCK_DGRAM, 0)) < 0) { + Com_Printf("NET_GetLocalAddress: Unable to create temporary socket, errno = %d\n", errno); + return; + } + + if (ioctl(interfaceSocket, SIOCGIFCONF, &ifc) != 0) { + Com_Printf("NET_GetLocalAddress: Unable to get list of network interfaces, errno = %d\n", errno); + return; + } + + + linkInterface = (struct ifreq *) ifc.ifc_buf; + while ((char *) linkInterface < &ifc.ifc_buf[ifc.ifc_len]) { + unsigned int nameLength; + + // The ioctl returns both the entries having the address (AF_INET) and the link layer entries (AF_LINK). The AF_LINK entry has the link layer address which contains the interface type. This is the only way I can see to get this information. We cannot assume that we will get bot an AF_LINK and AF_INET entry since the interface may not be configured. For example, if you have a 10Mb port on the motherboard and a 100Mb card, you may not configure the motherboard port. + + // For each AF_LINK entry... + if (linkInterface->ifr_addr.sa_family == AF_LINK) { + // if there is a matching AF_INET entry + inetInterface = (struct ifreq *) ifc.ifc_buf; + while ((char *) inetInterface < &ifc.ifc_buf[ifc.ifc_len]) { + if (inetInterface->ifr_addr.sa_family == AF_INET && + !strncmp(inetInterface->ifr_name, linkInterface->ifr_name, sizeof(linkInterface->ifr_name))) { + + for (nameLength = 0; nameLength < IFNAMSIZ; nameLength++) + if (!linkInterface->ifr_name[nameLength]) + break; + + sdl = (struct sockaddr_dl *)&linkInterface->ifr_addr; + // Skip loopback interfaces + if (sdl->sdl_type != IFT_LOOP) { + // Get the local interface address + strncpy(ifr.ifr_name, inetInterface->ifr_name, sizeof(ifr.ifr_name)); + if (ioctl(interfaceSocket, OSIOCGIFADDR, (caddr_t)&ifr) < 0) { + Com_Printf("NET_GetLocalAddress: Unable to get local address for interface '%s', errno = %d\n", inetInterface->ifr_name, errno); + } else { + struct sockaddr_in *sin; + int ip; + + sin = (struct sockaddr_in *)&ifr.ifr_addr; + + ip = ntohl(sin->sin_addr.s_addr); + localIP[ numIP ][0] = (ip >> 24) & 0xff; + localIP[ numIP ][1] = (ip >> 16) & 0xff; + localIP[ numIP ][2] = (ip >> 8) & 0xff; + localIP[ numIP ][3] = (ip >> 0) & 0xff; + Com_Printf( "IP: %i.%i.%i.%i (%s)\n", localIP[ numIP ][0], localIP[ numIP ][1], localIP[ numIP ][2], localIP[ numIP ][3], inetInterface->ifr_name); + numIP++; + } + } + + // We will assume that there is only one AF_INET entry per AF_LINK entry. + // What happens when we have an interface that has multiple IP addresses, or + // can that even happen? + // break; + } + inetInterface = IFR_NEXT(inetInterface); + } + } + linkInterface = IFR_NEXT(linkInterface); + } + + close(interfaceSocket); +} + +#else +void NET_GetLocalAddress( void ) { + char hostname[256]; + struct hostent *hostInfo; + // int error; // bk001204 - unused + char *p; + int ip; + int n; + + // Set this early so we can just return if there is an error + numIP = 0; + + 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; + } + + 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 ); + numIP++; + } +} +#endif + +/* +==================== +NET_OpenIP +==================== +*/ +// bk001204 - prototype needed +int NET_IPSocket (char *net_interface, int port); +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_IPSocket +==================== +*/ +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] || !Q_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, (sockaddr *)&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/codemp/unix/unix_shared.cpp b/codemp/unix/unix_shared.cpp new file mode 100644 index 0000000..fb6beed --- /dev/null +++ b/codemp/unix/unix_shared.cpp @@ -0,0 +1,350 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../game/q_shared.h" +#include "../qcommon/qcommon.h" + +typedef unsigned short DWORD; + +//============================================================================= + +// Used to determine CD Path +static char cdPath[MAX_OSPATH]; + +// Used to determine local installation path +static char installPath[MAX_OSPATH]; + +// Used to determine where to store user-specific files +static char homePath[MAX_OSPATH]; + +//DWORD timeGetTime(void) +//{ +// return Sys_Milliseconds(); +//} + +/* +================ +Sys_Milliseconds +================ +*/ +int curtime; +int sys_timeBase; +int Sys_Milliseconds (bool baseTime) +{ + 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; + + static int sys_timeBase = curtime; + if (!baseTime) + { + curtime -= sys_timeBase; + } + + return curtime; + +} + + +//#if 0 // bk001215 - see snapvector.nasm for replacement +#if (defined __APPLE__) // rcg010206 - using this for PPC builds... +long fastftol( float f ) { // bk001213 - from win32/win_shared.c + //static int tmp; + // __asm fld f + //__asm fistp tmp + //__asm mov eax, tmp + return (long)f; +} + +void Sys_SnapVector3( float *v ) { // bk001213 - see win32/win_shared.c + // bk001213 - old linux + v[0] = rint(v[0]); + v[1] = rint(v[1]); + v[2] = rint(v[2]); +} +#endif + + +void Sys_Mkdir( const char *path ) +{ + mkdir (path, 0777); +} + +char *strlwr (char *s) { + if ( s==NULL ) { // bk001204 - paranoia + assert(0); + return s; + } + while (*s) { + *s = tolower(*s); + s++; + } + return s; // bk001204 - duh +} + +//============================================ + +#define MAX_FOUND_FILES 0x1000 + +// bk001129 - new in 1.26 +void Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **list, int *numfiles ) { + char search[MAX_OSPATH], newsubdirs[MAX_OSPATH]; + char filename[MAX_OSPATH]; + DIR *fdir; + struct dirent *d; + struct stat st; + + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + return; + } + + if (strlen(subdirs)) { + Com_sprintf( search, sizeof(search), "%s/%s", basedir, subdirs ); + } + else { + Com_sprintf( search, sizeof(search), "%s", basedir ); + } + + if ((fdir = opendir(search)) == NULL) { + return; + } + + while ((d = readdir(fdir)) != NULL) { + Com_sprintf(filename, sizeof(filename), "%s/%s", search, d->d_name); + if (stat(filename, &st) == -1) + continue; + + if (st.st_mode & S_IFDIR) { + if (Q_stricmp(d->d_name, ".") && Q_stricmp(d->d_name, "..")) { + if (strlen(subdirs)) { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s/%s", subdirs, d->d_name); + } + else { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", d->d_name); + } + Sys_ListFilteredFiles( basedir, newsubdirs, filter, list, numfiles ); + } + } + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + break; + } + Com_sprintf( filename, sizeof(filename), "%s/%s", subdirs, d->d_name ); + if (!Com_FilterPath( filter, filename, qfalse )) + continue; + list[ *numfiles ] = CopyString( filename ); + (*numfiles)++; + } + + closedir(fdir); +} + +// bk001129 - in 1.17 this used to be +// char **Sys_ListFiles( const char *directory, const char *extension, int *numfiles, qboolean wantsubs ) +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, int *numfiles, qboolean wantsubs ) +{ + struct dirent *d; + // char *p; // bk001204 - unused + DIR *fdir; + qboolean dironly = wantsubs; + char search[MAX_OSPATH]; + int nfiles; + char **listCopy; + char *list[MAX_FOUND_FILES]; + //int flag; // bk001204 - unused + int i; + struct stat st; + + int extLen; + + if (filter) { + + nfiles = 0; + Sys_ListFilteredFiles( directory, "", filter, list, &nfiles ); + + list[ nfiles ] = 0; + *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; + } + + 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 = (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; +} + +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 Sys_SetDefaultCDPath(const char *path) +{ + Q_strncpyz(cdPath, path, sizeof(cdPath)); +} + +char *Sys_DefaultCDPath(void) +{ + return cdPath; +} + +void Sys_SetDefaultInstallPath(const char *path) +{ + Q_strncpyz(installPath, path, sizeof(installPath)); +} + +char *Sys_DefaultInstallPath(void) +{ + if (*installPath) + return installPath; + else + return Sys_Cwd(); +} + +void Sys_SetDefaultHomePath(const char *path) +{ + Q_strncpyz(homePath, path, sizeof(homePath)); +} + +char *Sys_DefaultHomePath(void) +{ + char *p; + + if (*homePath) + return homePath; + + if ((p = getenv("HOME")) != NULL) { + Q_strncpyz(homePath, p, sizeof(homePath)); +#ifdef MACOS_X + Q_strcat(homePath, sizeof(homePath), "/Library/Application Support/Quake3"); +#else + Q_strcat(homePath, sizeof(homePath), "/.jkii"); +#endif + if (mkdir(homePath, 0777)) { + if (errno != EEXIST) + Sys_Error("Unable to create directory \"%s\", error is %s(%d)\n", homePath, strerror(errno), errno); + } + return homePath; + } + return ""; // assume current dir +} + +//============================================ + +int Sys_GetProcessorId( void ) +{ + return CPUID_GENERIC; +} + +void Sys_ShowConsole( int visLevel, qboolean quitOnClose ) +{ +} + +char *Sys_GetCurrentUser( void ) +{ + struct passwd *p; + + if ( (p = getpwuid( getuid() )) == NULL ) { + return "player"; + } + return p->pw_name; +} diff --git a/codemp/unix/vm_x86.c b/codemp/unix/vm_x86.c new file mode 100644 index 0000000..7881443 --- /dev/null +++ b/codemp/unix/vm_x86.c @@ -0,0 +1,8 @@ + +#include "../qcommon/vm_local.h" + +void VM_Compile( vm_t *vm, vmHeader_t *header ) {} +int VM_CallCompiled( vm_t *vm, int *args ) {} + + + diff --git a/codemp/unix/vssver.scc b/codemp/unix/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..9ad194a7b7c1c7f8e3277f4a0cb4e5bf62b7ddf7 GIT binary patch literal 256 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZB>NVW>$H)K%|AF+UoBM+g8e}AZg_sx^m=-PK zfBL8j%x47hMO6<+|NiPc<(<;klR1HWb|C*7&#S86>5swk96-LA%F*A+Z(f1< zoIrkl%9_3Jk2Hb#TtI$_v}8`WZ$~mSP(L@2uNBi|lPWS1EWbjWfx(gSV*PeQ{{)~E F0|4K0Lk<7{ literal 0 HcmV?d00001 diff --git a/codemp/update_MPents.bat b/codemp/update_MPents.bat new file mode 100644 index 0000000..0df6253 --- /dev/null +++ b/codemp/update_MPents.bat @@ -0,0 +1 @@ +k:\util\EntityDefMaker w:\bin\mpsource\*.c w:\game\base\scripts\mp_entities.def diff --git a/codemp/vssver.scc b/codemp/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..09af4229c62b4a33674dac6ec88b0a8f88fc9f41 GIT binary patch literal 384 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiW!32oWL!~g-zKc|?Rem)BJ)9WkbF9j*rXHpJFwja%zq8!PpEp&x^P#dIU7)3254~d;jW{z zl^D!HW_PkMFf5w1iRogljtR)l$E*wtU(4rD-+OfzSpKH~1H)c_=j`2UCz}fa<$nSB zN21oBdGG$zTn)(o4dgekI6u9=S>2om$Y&5_V5rRe{6RD5BiMdMD8F^*d@!F0$}i{8 z1@ldTe4F``m=1esgY91owB)Rs``X4jc`$!1kl(>)=3&VbWX=UNK3$lBVdH1^HQQqo z!TO6u7#KdP=@{gyEd}#SfPAw&1I^-->%e?bF$M ja +// 0.0.11.0 07/17/2003 17:19:17 jmonroe build 0.11 for qa +// 0.0.10.1 07/16/2003 18:49:28 mgummelt General MP update +// 0.0.10.0 07/16/2003 10:45:33 jmonroe new product id +// 0.0.10.0 07/14/2003 19:38:41 jmonroe build 0.10 for qa +// 0.0.9.0 07/11/2003 17:49:06 jmonroe build 0.09 for qa +// 0.0.8.9 07/11/2003 14:38:31 rjohnson Update for siege stress test +// 0.0.8.8 07/11/2003 13:09:19 rjohnson general update for test +// 0.0.8.7 07/11/2003 10:52:01 rjohnson general update +// 0.0.8.6 07/10/2003 16:23:28 rjohnson New test build +// 0.0.8.5 07/10/2003 14:45:24 rjohnson General update +// 0.0.8.4 07/10/2003 13:28:42 rjohnson General update for test +// 0.0.8.3 07/09/2003 15:32:33 jmonroe fix mem leak on dlls +// 0.0.8.2 07/09/2003 14:18:14 rjohnson New test for release server bug finding +// 0.0.8.1 07/09/2003 13:29:39 rjohnson Update for modem test +// 0.0.8.0 07/09/2003 11:09:24 rjohnson General update +// 0.0.8.0 07/08/2003 16:47:56 jmonroe build 0.08 for qa +// 0.0.7.5 07/07/2003 14:42:27 rjohnson Update for test +// 0.0.7.4 07/05/2003 13:36:21 rjohnson new spawn points for duel +// 0.0.7.3 07/03/2003 16:27:31 rjohnson Update for rich's code +// 0.0.7.2 07/03/2003 16:01:36 rjohnson General update for test +// 0.0.7.1 07/03/2003 13:26:18 rjohnson General update for test +// 0.0.7.0 07/03/2003 11:41:17 rjohnson Update for Dan +// 0.0.7.0 07/02/2003 19:36:05 jmonroe build 7 for qa +// 0.0.6.7 07/02/2003 17:00:43 rjohnson General update for test +// 0.0.6.6 07/02/2003 14:57:45 rjohnson Update to fix spamming problem +// 0.0.6.5 07/02/2003 13:57:16 rjohnson Rich put in a default time limit so siege messages won't spam +// 0.0.6.4 07/02/2003 13:26:45 rjohnson General update for test +// 0.0.6.3 07/01/2003 14:40:06 rjohnson General update for test +// 0.0.6.2 06/30/2003 16:31:56 rjohnson General update for test +// 0.0.6.1 06/27/2003 14:30:37 rjohnson General update +// 0.0.6.0 06/26/2003 18:44:01 rjohnson update for bob +// 0.0.6.0 06/26/2003 17:37:02 jmonroe qa build +// 0.0.5.10 06/26/2003 14:23:59 rjohnson General update +// 0.0.5.9 06/26/2003 11:43:50 rjohnson General updates from everyone +// 0.0.5.8 06/25/2003 18:02:26 rjohnson Update for test +// 0.0.5.7 06/25/2003 16:22:06 rjohnson Update for test +// 0.0.5.6 06/25/2003 13:35:47 rjohnson Automap and seeker changes and... +// 0.0.5.5 06/25/2003 13:07:07 rjohnson Update +// 0.0.5.4 06/25/2003 10:44:28 jmonroe EAX 4.0 +// 0.0.5.3 06/24/2003 14:27:16 rjohnson General Update +// 0.0.5.2 06/23/2003 17:42:05 mgummelt Saber changes +// 0.0.5.1 06/23/2003 10:30:51 rjohnson Refresh .exe on the net +// 0.0.5.0 06/22/2003 16:44:05 mgummelt Rancor & Reborn/Cultist tweaks +// 0.0.4.10 06/20/2003 14:26:46 rjohnson Update for 2:30 test +// 0.0.4.9 06/20/2003 12:51:17 rjohnson Test of nathan's concept to show scores during duel matches +// 0.0.4.8 06/19/2003 18:03:49 rjohnson General upate +// 0.0.4.7 06/19/2003 14:22:45 rjohnson General update synch for gil +// 0.0.4.6 06/19/2003 12:02:20 mgummelt Lightsaber Damage Fixed +// 0.0.4.5 06/19/2003 11:49:13 mgummelt Fighter Control Scheme tweak +// 0.0.4.4 06/19/2003 10:31:20 mgummelt Vehicle Fighter Controls 4.0 (fixed build) +// 0.0.4.3 06/19/2003 01:40:18 mgummelt Vehicle Control Scheme 4.0 +// 0.0.4.2 06/18/2003 16:33:01 rjohnson With new and improved Gil compiling optimization settings (blame him!) +// 0.0.4.1 06/17/2003 17:43:56 rjohnson Test to see if in game menu works +// 0.0.4.0 06/17/2003 14:54:59 rjohnson General update +// 0.0.4.0 06/14/2003 01:45:27 jmonroe inc version to synch with qa beta +// 0.0.2.7 06/13/2003 17:30:44 rjohnson General update +// 0.0.2.6 06/13/2003 16:54:54 rjohnson Fix for siege crash +// 0.0.2.5 06/13/2003 14:47:35 rjohnson New stuff +// 0.0.2.3 06/12/2003 17:05:57 rjohnson Rate test +// 0.0.2.2 06/12/2003 15:46:11 rjohnson Entity reorg, general updates +// 0.0.2.1 06/11/2003 13:28:50 rjohnson General new version stuff - lots of stuff by everyone +// 0.0.2.0 06/11/2003 01:25:29 jmonroe material based footstep sounds ported from sp +// 0.0.2.0 06/08/2003 00:18:19 jmonroe CGEN_LIGHTING_DIFFUSE_ENTITY merges lightingdiffuse and entity color +// 0.0.1.16 06/07/2003 22:31:17 rwhitehouse Saber moves sync'd up with SP, skin/model fix (related to hunk switch to Z_Malloc) +// 0.0.1.15 06/06/2003 23:05:55 rwhitehouse Vehicle controls suck somewhat less. (MP) +// 0.0.1.14 06/05/2003 14:13:10 rwhitehouse swoop changes, attempted vehicle spectator fix +// 0.0.1.13 06/04/2003 15:46:11 rwhitehouse The hunk has been removed entirely. +// 0.0.1.12 06/03/2003 13:50:29 rwhitehouse swoop choppiness fix +// 0.0.1.11 06/03/2003 13:26:46 rwhitehouse swoop infinite loop fix +// 0.0.1.10 06/03/2003 11:18:27 rwhitehouse radar siege item fixes +// 0.0.1.9 06/02/2003 16:05:35 rwhitehouse new radar flags for siege items and siege objective entities +// 0.0.1.8 06/02/2003 14:37:56 rwhitehouse Swoop bug fixes, cartwheel/arial specials added for all saber stances, butterfly attack added for saber staff. +// 0.0.1.7 06/01/2003 21:33:52 rwhitehouse anim bug fixes, swoop fixes. +// 0.0.1.6 05/28/2003 17:22:11 rwhitehouse swoop fixes +// 0.0.1.5 05/28/2003 11:33:02 rwhitehouse Swoop less horribly broken +// 0.0.1.4 05/25/2003 19:22:02 rwhitehouse some dlight stuff.. and other things I forget +// 0.0.1.2 05/22/2003 16:28:21 rwhitehouse Stencil shadows fixed, vehicle precaching fixed +// 0.0.1.1 05/14/2003 16:14:42 jmonroe added version blocks +// END COMMENTS + +#endif // __AUTO_VERSION_HEADER diff --git a/codemp/win32/JK2cgame.rc b/codemp/win32/JK2cgame.rc new file mode 100644 index 0000000..d280a1b --- /dev/null +++ b/codemp/win32/JK2cgame.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", "JA Client Game DLL" + VALUE "FileVersion", VERSION_STRING + VALUE "InternalName", "JAcgame" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "JAcgame.dll" + VALUE "ProductName", "Jedi Knight®: Jedi Academy (MP)" + 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/codemp/win32/JK2game.rc b/codemp/win32/JK2game.rc new file mode 100644 index 0000000..f342486 --- /dev/null +++ b/codemp/win32/JK2game.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", "JA Game DLL" + VALUE "FileVersion", VERSION_STRING + VALUE "InternalName", "JAgame" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "JAgame.dll" + VALUE "ProductName", "Jedi Knight®: Jedi Academy (MP)" + 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/codemp/win32/WinDed.rc b/codemp/win32/WinDed.rc new file mode 100644 index 0000000..366208d --- /dev/null +++ b/codemp/win32/WinDed.rc @@ -0,0 +1,105 @@ +// 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 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "Raven Software" + VALUE "CompanyName", "Activision Inc" + VALUE "FileDescription", "Jedi Academy MP Dedicated Server" + VALUE "FileVersion", VERSION_STRING + VALUE "InternalName", "JA MP Ded" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "JAMPDed.exe" + VALUE "ProductName", "Jedi Knight®: Jedi Academy Dedicated Server" + 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/codemp/win32/dbg_console_xbox.cpp b/codemp/win32/dbg_console_xbox.cpp new file mode 100644 index 0000000..021f031 --- /dev/null +++ b/codemp/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/codemp/win32/dbg_console_xbox.h b/codemp/win32/dbg_console_xbox.h new file mode 100644 index 0000000..c6b3732 --- /dev/null +++ b/codemp/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/codemp/win32/glw_win.h b/codemp/win32/glw_win.h new file mode 100644 index 0000000..4fbef00 --- /dev/null +++ b/codemp/win32/glw_win.h @@ -0,0 +1,33 @@ +#ifndef _WIN32 +# error You should not be including this file on this platform +#endif + +#ifndef __GLW_WIN_H__ +#define __GLW_WIN_H__ +#include + +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; + +bool GL_CheckForExtension(const char *ext); + +#endif diff --git a/codemp/win32/glw_win_dx8.h b/codemp/win32/glw_win_dx8.h new file mode 100644 index 0000000..1098f61 --- /dev/null +++ b/codemp/win32/glw_win_dx8.h @@ -0,0 +1,180 @@ + +/* + * 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 + IDirect3DDevice8* 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 + { + IDirect3DTexture8* mipmap; + D3DTEXTUREFILTERTYPE minFilter, mipFilter, magFilter; + D3DTEXTUREADDRESS wrapU, wrapV; + float anisotropy; + }; + + 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 + D3DVIEWPORT8 viewport; + + // Clearing info + D3DCOLOR clearColor; + float clearDepth; + int clearStencil; + + // Widescreen mode + bool isWidescreen; + + // Global color + D3DCOLOR currentColor; + + // Scissoring + bool scissorEnable; + D3DRECT scissorBox; + + // Directional Light + D3DLIGHT8 dirLight; + D3DMATERIAL8 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(); +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/codemp/win32/qe3.ico b/codemp/win32/qe3.ico new file mode 100644 index 0000000000000000000000000000000000000000..ac9cf1f0898020aff4efec371b6e97aeedbe5391 GIT binary patch literal 3638 zcmeH}KWH0Q9LIkyE%cU<)tR!$S2Y%`PSlo36iTgt?a75YURzHElPrR8cZU0;J7v(t zHh9PwGL%x}$xCoHlfi}#nKT7YGD(Up$%>;xhC;5NcZwY-_0pw7^Um+?z4!gR-+TAF zr{B*aBl3w{zAS0^`7@C*T34?Q`@elIvc}jKbQPk8M}$FDMuv~`_IckE;ZHno_~1@c zc6N4TXMaa}zyB)DULsqqMAn*#Ebk^VvzFT>BkXJF+W6<**4Uf^Y34&e9TH{sXeEAWf( z3zEnzdg=oAA5v+wcwe2K)xR4Zj3G2XDY9@CkeZpTH;Z3H&>*eXgfm z54kE_^C{nhZ^1X=cj33;mGuVn2DA;m1U(0BVE+Jq4}KGV9lip;2)_V73txiI!)M?} z;jz251-}Nr3_k;(xxhOj$pQQx{3iT5dO~GZIXRJ6uU^TE7cb=b^XKyH*)w_a z2G#kOs^d-tv^EiK9I+qY$IZcc9AyeWoZ$k^DJT)A>ZE?v4L zy+0HAS_x)=wYY)m9R9i~Dih?@yidr+CI9IarP?pTk3h7{glpGQ8D&ooc$PQKgQYrcbq+o2g7H4@|MV?;_ZhL=caNo zH_CXqTvX4##5pyppqR*<9m!?0`iD}?(a)w~S!jKk&5n=gda+p4Cv<&svY;1pBTP!? zqhCWW>U#OstvAzpq1>A;=u=poO83A@<5}hw3e$yNuTvQ7_PS&Ane%>+gnGSL9wXHp zKj;`nr`s_KW$X>-ODyQ+W5f8)=thV6{88r!vdo-eAeI>Rn9On7=?q91gTq1II6f{E z%A_E|a@;TKh0bZdba*%@-6-97{rcZlvY<^{)o)c0O+y zJv%NMV+p4yrh}>5V*q4URe6ptN z`RVL12wJQYwa|HsAW$z?k0Ni?XILSYS#{kO*44g((6$|i#eF|co#SBo{jRWUho-Gm zB`f#gJ;$kAX=1KZl#ZIp=tKXKoI|VbI8Nl)rnQ2oX7hQ^0Cd|)Kgx<}k*9}ps;3t0 zT1cYDuIrd-N@uP*{mOs>R;+Nis^?gDRLV~empVQQAo*M7irSM;+8^q}(|ZP>r>p*5 zR~D2B*RmWQ{g{^R4vn9ke?_==hP#U2qI(?L_aa+VyNu6t;XjTQ^z^rP&irSMzW{)< Ba*zN3 literal 0 HcmV?d00001 diff --git a/codemp/win32/resource.h b/codemp/win32/resource.h new file mode 100644 index 0000000..c5d1f46 --- /dev/null +++ b/codemp/win32/resource.h @@ -0,0 +1,23 @@ +//{{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 +#define IDC_CURSOR2 130 +#define IDC_CURSOR3 131 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 132 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/codemp/win32/snd_fx_img.h b/codemp/win32/snd_fx_img.h new file mode 100644 index 0000000..d226885 --- /dev/null +++ b/codemp/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/codemp/win32/ui.rc b/codemp/win32/ui.rc new file mode 100644 index 0000000..bf5f5a1 --- /dev/null +++ b/codemp/win32/ui.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", "JA User Interface DLL" + VALUE "FileVersion", VERSION_STRING + VALUE "InternalName", "ui" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "ui.dll" + VALUE "ProductName", "Jedi Knight®: Jedi Academy (MP)" + 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/codemp/win32/vssver.scc b/codemp/win32/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..bd69e451da0e6966ac53f647ac2ea717adf2f574 GIT binary patch literal 640 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiY;IbZvlhlv3Uc!Bhr`u!W7YvYqaLVQ3XbeHk0 z{5r{G79gJ=$hS4(vtH??n#>5~3jq1*K{tHfFG@%S$qNGcODi@>{Wh4C#0=yM0r_)o zxZiUu-7&I<4;vw_XWNVgvF;fc(|Vj;(pQf)lJ>6v#g{v+sZU4~ZlZpu8B6 zUwH1+p2&pbsT@GQIFPS3%ipeF_fsl6kS_t`PvsQM{Jd!**nUYMpK~quxoGS8sUZ8M zfPDS1+MT>Tn^HmcNdx({Kh4f4);WXCYXn+SH(l8)T|YCK6)4{XITn?(YQhkNNi!|5B*m zz4K{mVEG;(U*XR6`JBE+Nt{6ay+D4FWNBFKrHiQ`_xAz$EmpkelgS^y4@gQ5%!-W&3ouIhS# +#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 0 // VVFIXME + 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/codemp/win32/win_filecode.cpp b/codemp/win32/win_filecode.cpp new file mode 100644 index 0000000..3528052 --- /dev/null +++ b/codemp/win32/win_filecode.cpp @@ -0,0 +1,345 @@ + +/* + * 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; + in = fopen("d:\\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 + s_Files->Insert(info, code); + } + + fclose(in); + Z_Free(baseAddr); + return true; +} + +bool Sys_SaveFileCodes(void) +{ + bool ret; + int res; + + // 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; + + // 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(); + + // 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_BuildOSPath(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/codemp/win32/win_gamma.cpp b/codemp/win32/win_gamma.cpp new file mode 100644 index 0000000..88272e9 --- /dev/null +++ b/codemp/win32/win_gamma.cpp @@ -0,0 +1,165 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +/* +** WIN_GAMMA.C +*/ +#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 = (qboolean)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; + Com_Printf (S_COLOR_YELLOW "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; + + Com_Printf (S_COLOR_YELLOW "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; + } + } + } + } +} + +/* +void mapGammaMax( void ) { + int i, j; + unsigned short table[3][256]; + + // try to figure out what win2k will let us get away with setting + for ( i = 0 ; i < 256 ; i++ ) { + if ( i >= 128 ) { + table[0][i] = table[1][i] = table[2][i] = 0xffff; + } else { + table[0][i] = table[1][i] = table[2][i] = i<<9; + } + } + + for ( i = 0 ; i < 128 ; i++ ) { + for ( j = i*2 ; j < 255 ; j++ ) { + table[0][i] = table[1][i] = table[2][i] = j<<8; + if ( !SetDeviceGammaRamp( glw_state.hDC, table ) ) { + break; + } + } + table[0][i] = table[1][i] = table[2][i] = i<<9; + Com_Printf( "index %i max: %i\n", i, j-1 ); + } +} +*/ + +/* +** 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/codemp/win32/win_gamma_console.cpp b/codemp/win32/win_gamma_console.cpp new file mode 100644 index 0000000..309845c --- /dev/null +++ b/codemp/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/codemp/win32/win_glimp.cpp b/codemp/win32/win_glimp.cpp new file mode 100644 index 0000000..9c367ff --- /dev/null +++ b/codemp/win32/win_glimp.cpp @@ -0,0 +1,2095 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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 "resource.h" +#include "glw_win.h" +#include "win_local.h" +#include "../qcommon/stringed_ingame.h" +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 (MP)" + +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 + +// 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: + Com_Printf("...WARNING: fullscreen unavailable in this mode\n" ); + return qfalse; + case RSERR_INVALID_MODE: + Com_Printf ("...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; + + Com_Printf ("...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 ) + { + Com_Printf (S_COLOR_YELLOW "...numPFDs > MAX_PFDS (%d > %d)\n", maxPFD, MAX_PFDS ); + maxPFD = MAX_PFDS; + } + + Com_Printf ("...%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 ) + { + Com_Printf ("...PFD %d rejected, software acceleration\n", i ); + } + continue; + } + } + + // verify pixel type + if ( pfds[i].iPixelType != PFD_TYPE_RGBA ) + { + if ( r_verbose->integer ) + { + Com_Printf ("...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 ) + { + Com_Printf ("...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 ) + { + Com_Printf ("...no hardware acceleration found\n" ); + return 0; + } + else + { + Com_Printf ("...using software emulation\n" ); + } + } + else if ( pfds[bestMatch].dwFlags & PFD_GENERIC_ACCELERATED ) + { + Com_Printf ("...MCD acceleration found\n" ); + } + else + { + Com_Printf ("...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 ) + { + Com_Printf ("...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 ) + { + Com_Printf ("...GLW_ChoosePFD failed\n"); + return TRY_PFD_FAIL_SOFT; + } + Com_Printf ("...PIXELFORMAT %d selected\n", pixelformat ); + + DescribePixelFormat( glw_state.hDC, pixelformat, sizeof( *pPFD ), pPFD ); + + if ( SetPixelFormat( glw_state.hDC, pixelformat, pPFD ) == FALSE ) + { + Com_Printf ( "...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 ) + { + Com_Printf ("...creating GL context: " ); + if ( ( glw_state.hGLRC = qwglCreateContext( glw_state.hDC ) ) == 0 ) + { + Com_Printf ( "failed\n"); + + return TRY_PFD_FAIL_HARD; + } + Com_Printf ("succeeded\n" ); + + Com_Printf ("...making context current: " ); + if ( !qwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) ) + { + qwglDeleteContext( glw_state.hGLRC ); + glw_state.hGLRC = NULL; + Com_Printf ( "failed\n"); + return TRY_PFD_FAIL_HARD; + } + Com_Printf ("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 + + Com_Printf ("Initializing OpenGL driver\n" ); + + // + // get a DC for our window if we don't already have one allocated + // + if ( glw_state.hDC == NULL ) + { + Com_Printf ("...getting DC: " ); + + if ( ( glw_state.hDC = GetDC( g_wv.hWnd ) ) == NULL ) + { + Com_Printf ("failed\n" ); + return qfalse; + } + Com_Printf ("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, (qboolean)r_stereo->integer ); + if ( ( tpfd = GLW_MakeContext( &pfd ) ) != TRY_PFD_SUCCESS ) + { + if ( tpfd == TRY_PFD_FAIL_HARD ) + { + Com_Printf (S_COLOR_YELLOW "...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; + + Com_Printf ("...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, (qboolean)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; + } + + Com_Printf ("...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 ) ) + { + Com_Printf ("...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)); + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = 0;//(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; +// Com_Printf ("...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, + WINDOW_CLASS_NAME, + 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 ); + Com_Printf ("...created window@%d,%d (%dx%d)\n", x, y, w, h ); + } + else + { + Com_Printf ("...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: + Com_Printf ("restart required\n" ); + break; + case DISP_CHANGE_BADPARAM: + Com_Printf ("bad param\n" ); + break; + case DISP_CHANGE_BADFLAGS: + Com_Printf ("bad flags\n" ); + break; + case DISP_CHANGE_FAILED: + Com_Printf ("DISP_CHANGE_FAILED\n" ); + break; + case DISP_CHANGE_BADMODE: + Com_Printf ("bad mode\n" ); + break; + case DISP_CHANGE_NOTUPDATED: + Com_Printf ("not updated\n" ); + break; + default: + Com_Printf ("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 + // + Com_Printf ("...setting mode %d:", mode ); + if ( !R_GetModeInfo( &glConfig.vidWidth, &glConfig.vidHeight, mode ) ) + { + Com_Printf (" invalid mode\n" ); + return RSERR_INVALID_MODE; + } + Com_Printf (" %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; + Com_Printf ("...using colorsbits of %d\n", colorbits ); + } + else + { + Com_Printf ("WARNING:...changing depth not supported on Win95 < pre-OSR 2.x\n" ); + } + } + else + { + Com_Printf ("...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 ) + { + Com_Printf ("...already fullscreen, avoiding redundant CDS\n" ); + + if ( !GLW_CreateWindow ( glConfig.vidWidth, glConfig.vidHeight, colorbits, qtrue ) ) + { + Com_Printf ("...restoring display settings\n" ); + ChangeDisplaySettings( 0, 0 ); + return RSERR_INVALID_MODE; + } + } + // + // need to call CDS + // + else + { + Com_Printf ("...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 ) + { + Com_Printf ("ok\n" ); + + if ( !GLW_CreateWindow ( glConfig.vidWidth, glConfig.vidHeight, colorbits, qtrue) ) + { + Com_Printf ("...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; + + Com_Printf ("failed, " ); + + PrintCDSError( cdsRet ); + + Com_Printf ("...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 ) + { + Com_Printf (" ok\n" ); + if ( !GLW_CreateWindow( glConfig.vidWidth, glConfig.vidHeight, colorbits, qtrue) ) + { + Com_Printf ("...restoring display settings\n" ); + ChangeDisplaySettings( 0, 0 ); + return RSERR_INVALID_MODE; + } + + glw_state.cdsFullscreen = qtrue; + } + else + { + Com_Printf (" failed, " ); + + PrintCDSError( cdsRet ); + + Com_Printf ("...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; +} + +/* +** GLW_CheckForExtension + + Cannot use strstr directly to differentiate between (for eg) reg_combiners and reg_combiners2 +*/ + +bool GL_CheckForExtension(const char *ext) +{ + char *temp; + char term; + + temp = strstr(glConfig.extensions_string, ext); + if(!temp) + { + return(false); + } + // String exists but it may not be terminated + term = temp[strlen(ext)]; + if((term == ' ') || !term) + { + return(true); + } + return(false); +} + +//-------------------------------------------- +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 ) + { + Com_Printf ("...GL_S3_s3tc available\n" ); + } + + if ( newer_tc ) + { + Com_Printf ("...GL_EXT_texture_compression_s3tc available\n" ); + } + + if ( !r_ext_compressed_textures->value ) + { + // Compressed textures are off + glConfig.textureCompression = TC_NONE; + Com_Printf ("...ignoring texture compression\n" ); + } + else if ( !old_tc && !newer_tc ) + { + // Requesting texture compression, but no method found + glConfig.textureCompression = TC_NONE; + Com_Printf ("...no supported texture compression method found\n" ); + Com_Printf (".....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 ) + { + Com_Printf ("...no tc preference specified\n" ); + Com_Printf (".....using GL_EXT_texture_compression_s3tc\n" ); + glConfig.textureCompression = TC_S3TC_DXT; + } + else + { + Com_Printf ("...no tc preference specified\n" ); + Com_Printf (".....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 ) + { + Com_Printf ("...using preferred tc method, GL_S3_s3tc\n" ); + glConfig.textureCompression = TC_S3TC; + } + else + { + Com_Printf ("...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 ) + { + Com_Printf ("...using GL_S3_s3tc\n" ); + glConfig.textureCompression = TC_S3TC; + } + else + { + // Drat, preference can't be honored + Com_Printf ("...preferred tc method, GL_S3_s3tc not available\n" ); + Com_Printf (".....falling back to GL_EXT_texture_compression_s3tc\n" ); + glConfig.textureCompression = TC_S3TC_DXT; + } + } + else + { + // Preferring to user newer compression + if ( newer_tc ) + { + Com_Printf ("...using GL_EXT_texture_compression_s3tc\n" ); + glConfig.textureCompression = TC_S3TC_DXT; + } + else + { + // Drat, preference can't be honored + Com_Printf ("...preferred tc method, GL_EXT_texture_compression_s3tc not available\n" ); + Com_Printf (".....falling back to GL_S3_s3tc\n" ); + glConfig.textureCompression = TC_S3TC; + } + } + } + } + } +} + +/* +** GLW_InitExtensions +*/ +static void GLW_InitExtensions( void ) +{ + if ( !r_allowExtensions->integer ) + { + Com_Printf ("*** IGNORING OPENGL EXTENSIONS ***\n" ); + g_bDynamicGlowSupported = false; + Cvar_Set( "r_DynamicGlow","0" ); + return; + } + + Com_Printf ("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; + Com_Printf ("...using GL_EXT_texture_env_add\n" ); + } + else + { + glConfig.textureEnvAddAvailable = qfalse; + Com_Printf ("...ignoring GL_EXT_texture_env_add\n" ); + } + } + else + { + Com_Printf ("...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; + Com_Printf ("...Using GL_EXT_texture_edge_clamp\n" ); + } + + + // WGL_EXT_swap_control + qwglSwapIntervalEXT = ( BOOL (WINAPI *)(int)) qwglGetProcAddress( "wglSwapIntervalEXT" ); + if ( qwglSwapIntervalEXT ) + { + Com_Printf ("...using WGL_EXT_swap_control\n" ); + r_swapInterval->modified = qtrue; // force a set next frame + } + else + { + Com_Printf ("...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 ) + { + Com_Printf ("...using GL_ARB_multitexture\n" ); + } + else + { + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = NULL; + qglClientActiveTextureARB = NULL; + Com_Printf ("...not using GL_ARB_multitexture, < 2 texture units\n" ); + } + } + } + else + { + Com_Printf ("...ignoring GL_ARB_multitexture\n" ); + } + } + else + { + Com_Printf ("...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 ) + { + Com_Printf ("...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 + { + Com_Printf ("...ignoring GL_EXT_compiled_vertex_array\n" ); + } + } + else + { + Com_Printf ("...GL_EXT_compiled_vertex_array not found\n" ); + } + + qglPointParameterfEXT = NULL; + qglPointParameterfvEXT = NULL; + + //3d textures -rww + qglTexImage3DEXT = NULL; + qglTexSubImage3DEXT = NULL; + + if ( strstr( glConfig.extensions_string, "GL_EXT_point_parameters" ) ) + { + if ( r_ext_compiled_vertex_array->integer || 1) + { + Com_Printf ("...using GL_EXT_point_parameters\n" ); + qglPointParameterfEXT = ( void ( APIENTRY * )( GLenum, GLfloat) ) qwglGetProcAddress( "glPointParameterfEXT" ); + qglPointParameterfvEXT = ( void ( APIENTRY * )( GLenum, GLfloat *) ) qwglGetProcAddress( "glPointParameterfvEXT" ); + + //3d textures -rww + qglTexImage3DEXT = (void ( APIENTRY * ) (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *) ) qwglGetProcAddress( "glTexImage3DEXT" ); + qglTexSubImage3DEXT = (void ( APIENTRY * ) (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *) ) qwglGetProcAddress( "glTexSubImage3DEXT" ); + + if (!qglPointParameterfEXT || !qglPointParameterfvEXT) + { + Com_Error (ERR_FATAL, "bad getprocaddress"); + } + } + else + { + Com_Printf ("...ignoring GL_EXT_point_parameters\n" ); + } + } + else + { + Com_Printf ("...GL_EXT_point_parameters 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" ); + } + + // Fragment Programs. + bool bARBFragmentProgram = false; + 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 + { + Com_Printf ("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 = (qboolean)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 on an ICD, + // try it again but with a 16-bit desktop + if ( r_colorbits->integer != 16 || + cdsFullscreen != qtrue || + r_mode->integer != 3 ) + { + if ( !GLW_StartDriverAndSetMode( 3, 16, qtrue ) ) + { + goto fail; + } + } + } + +#ifdef _CRAZY_ATTRIB_DEBUG + //I can get away with this because we don't actually use push/pop attrib anywhere else. + qglPushAttrib(GL_ACCUM_BUFFER_BIT|GL_COLOR_BUFFER_BIT|GL_CURRENT_BIT|GL_DEPTH_BUFFER_BIT| + GL_ENABLE_BIT|GL_EVAL_BIT|GL_FOG_BIT|GL_HINT_BIT|GL_LIGHTING_BIT|GL_LINE_BIT|GL_LIST_BIT| + GL_PIXEL_MODE_BIT|GL_POINT_BIT|GL_POLYGON_BIT|GL_POLYGON_STIPPLE_BIT|GL_SCISSOR_BIT| + GL_STENCIL_BUFFER_BIT|GL_TEXTURE_BIT|GL_TRANSFORM_BIT|GL_VIEWPORT_BIT); +#endif + + 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( (qboolean)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; + +// Com_Printf ("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 + // + Q_strncpyz( buf, glConfig.renderer_string, sizeof(buf) ); + 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 + } + //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; + } + + Com_Printf ("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; + +// Com_Printf ("...wglMakeCurrent( NULL, NULL ): %s\n", success[retVal] ); + } + + // delete HGLRC + if ( glw_state.hGLRC ) + { + retVal = qwglDeleteContext( glw_state.hGLRC ) != 0; +// Com_Printf ("...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; +// Com_Printf ("...releasing DC: %s\n", success[retVal] ); + glw_state.hDC = NULL; + } + + // destroy window + if ( g_wv.hWnd ) + { +// Com_Printf ("...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 ) + { +// Com_Printf ("...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 ); + } +} + + +/* +=========================================================== + +SMP acceleration + +=========================================================== +*/ + +HANDLE renderCommandsEvent; +HANDLE renderCompletedEvent; +HANDLE renderActiveEvent; + +void (*glimpRenderThread)( void ); + +void GLimp_RenderThreadWrapper( void ) { + glimpRenderThread(); + + // unbind the context before we die + qwglMakeCurrent( glw_state.hDC, NULL ); +} + +/* +======================= +GLimp_SpawnRenderThread +======================= +*/ +HANDLE renderThreadHandle; +int renderThreadId; +qboolean GLimp_SpawnRenderThread( void (*function)( void ) ) { + + renderCommandsEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + renderCompletedEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + renderActiveEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); + + glimpRenderThread = function; + + renderThreadHandle = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + (LPTHREAD_START_ROUTINE)GLimp_RenderThreadWrapper, // LPTHREAD_START_ROUTINE lpStartAddr, + 0, // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + (unsigned long *)&renderThreadId ); + + if ( !renderThreadHandle ) { + return qfalse; + } + + return qtrue; +} + +static void *smpData; +static int wglErrors; + +void *GLimp_RendererSleep( void ) { + void *data; + + if ( !qwglMakeCurrent( glw_state.hDC, NULL ) ) { + wglErrors++; + } + + ResetEvent( renderActiveEvent ); + + // after this, the front end can exit GLimp_FrontEndSleep + SetEvent( renderCompletedEvent ); + + WaitForSingleObject( renderCommandsEvent, INFINITE ); + + if ( !qwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) ) { + wglErrors++; + } + + ResetEvent( renderCompletedEvent ); + ResetEvent( renderCommandsEvent ); + + data = smpData; + + // after this, the main thread can exit GLimp_WakeRenderer + SetEvent( renderActiveEvent ); + + return data; +} + + +void GLimp_FrontEndSleep( void ) { + WaitForSingleObject( renderCompletedEvent, INFINITE ); + + if ( !qwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ) ) { + wglErrors++; + } +} + + +void GLimp_WakeRenderer( void *data ) { + smpData = data; + + if ( !qwglMakeCurrent( glw_state.hDC, NULL ) ) { + wglErrors++; + } + + // after this, the renderer can continue through GLimp_RendererSleep + SetEvent( renderCommandsEvent ); + + WaitForSingleObject( renderActiveEvent, INFINITE ); +} + + +// Allocate and create a new PBuffer. +bool CPBUFFER::Create( int iWidth, int iHeight, int iColorBits, int iDepthBits, int iStencilBits ) +{ + m_iWidth = iWidth; + m_iHeight = iHeight; + m_iColorBits = iColorBits; + m_iDepthBits = iDepthBits; + m_iStencilBits = iStencilBits; + + extern glwstate_t glw_state; + m_hOldRC = glw_state.hGLRC; //qwglGetCurrentContext(); + + // Get the current device context. + m_hOldDC = glw_state.hDC; //qwglGetCurrentDC(); + + if( !m_hOldDC ) + { + return false; + } + +#define WGL_BIND_TO_TEXTURE_RGB_ARB 0x2070 + + // These are standard settings. I suppose if one wanted more control you could pass an attrib list in (but why?). + const int iAttribList[] = + { + WGL_SUPPORT_OPENGL_ARB, true, // P-buffer will be used with OpenGL. + WGL_DRAW_TO_PBUFFER_ARB, true, // Enable render to p-buffer. + WGL_BIND_TO_TEXTURE_RGBA_ARB, true, // P-buffer will be used as a texture. + WGL_RED_BITS_ARB, 8, // At least 8 bits for RED channel. + WGL_GREEN_BITS_ARB, 8, // At least 8 bits for GREEN channel. + WGL_BLUE_BITS_ARB, 8, // At least 8 bits for BLUE channel. + WGL_ALPHA_BITS_ARB, 8, // At least 8 bits for ALPHA channel. + WGL_DEPTH_BITS_ARB, 16, // At least 16 bits for depth buffer. + WGL_DOUBLE_BUFFER_ARB, false, // We don't require double buffering + 0 // Zero terminates the list. + }; + +#define WGL_TEXTURE_RECTANGLE_NV 0x20A2 + + //const float fAttribList[] = { 0 }; + const int iFlags[] = + { + WGL_TEXTURE_FORMAT_ARB, WGL_TEXTURE_RGBA_ARB, // Our p-buffer will have a texture format of RGBA. + WGL_TEXTURE_TARGET_ARB, WGL_TEXTURE_RECTANGLE_NV, // Texture target will be GL_TEXTURE_RECTANGLE. + 0 // Zero terminates the list. + }; + + // Choose pixel format. + unsigned int numFormats; + GLint pixelFormat; + if( !qwglChoosePixelFormatARB( m_hOldDC, iAttribList, NULL, 1, &pixelFormat, &numFormats ) ) + { + return false; + } + + // Create the pbuffer. + m_hBuffer = qwglCreatePbufferARB( m_hOldDC, pixelFormat, m_iWidth, m_iHeight, iFlags ); + if( !m_hBuffer ) + { + return false; + } + + // Get the pbuffer's device context. + m_hDC = qwglGetPbufferDCARB( m_hBuffer ); + if( !m_hDC ) + { + return false; + } + + // Create a rendering context for the pbuffer. + m_hRC = qwglCreateContext( m_hDC ); + if( !m_hRC ) + { + return false; + } + + // Share Display Lists and Texture Objects between contexts (NOTE: Could + // also just use parent app RC). + qwglShareLists( m_hOldRC, m_hRC ); + + // Set and output the actual pBuffer dimensions. + qwglQueryPbufferARB( m_hBuffer, WGL_PBUFFER_WIDTH_ARB, &m_iWidth ); + qwglQueryPbufferARB( m_hBuffer, WGL_PBUFFER_HEIGHT_ARB, &m_iHeight ); + + + // Create the PBuffer Texture. + extern int giTextureBindNum; + m_uiPBufferTexture = 1024 + giTextureBindNum++; + + qglDisable( GL_TEXTURE_2D ); + qglEnable( GL_TEXTURE_RECTANGLE_EXT ); + + /*int *pTexture = new int [m_iWidth * m_iHeight]; + memset( pTexture, 0, m_iWidth * m_iHeight * sizeof( int ) ); */ + + qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, m_uiPBufferTexture ); + //qglTexImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGB, m_iWidth, m_iHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, pTexture ); + qglTexImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGB, m_iWidth, m_iHeight, 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 ); + + //delete [] pTexture; + + return true; +} + +// Destroy and deallocate a PBuffer. +void CPBUFFER::Destroy() +{ + // Release the pbuffer texture. + qglDeleteTextures( 1, &m_uiPBufferTexture ); + + // Release RC. + if( m_hRC ) + { + qwglDeleteContext( m_hRC ); + m_hRC = NULL; + } + + // Release DC. + if( m_hDC ) + { + qwglReleasePbufferDCARB( m_hBuffer, m_hDC); + m_hDC = NULL; + } + + // Release PBuffer. + qwglDestroyPbufferARB( m_hBuffer ); +} + +// Make this PBuffer the current render device. +bool CPBUFFER::Begin() +{ + if( !qwglMakeCurrent( m_hDC, m_hRC ) ) + { + return false; + } + + qwglCopyContext( m_hOldRC, m_hRC, GL_ALL_ATTRIB_BITS ); + + return true; +} + +// Restore the previous render device. +bool CPBUFFER::End() +{ + if( !qwglMakeCurrent( m_hOldDC, m_hOldRC ) ) + { + return false; + } + + return true; +} diff --git a/codemp/win32/win_glimp_console.cpp b/codemp/win32/win_glimp_console.cpp new file mode 100644 index 0000000..203738c --- /dev/null +++ b/codemp/win32/win_glimp_console.cpp @@ -0,0 +1,281 @@ +// 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.maxTextureFilterAnisotropy = 0; + if ( strstr( glConfig.extensions_string, "EXT_texture_filter_anisotropic" ) ) + { + 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; + } + + // 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 + Com_Printf ("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/codemp/win32/win_input.cpp b/codemp/win32/win_input.cpp new file mode 100644 index 0000000..79ad8ae --- /dev/null +++ b/codemp/win32/win_input.cpp @@ -0,0 +1,1141 @@ +// win_input.c -- win32 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../client/client.h" +#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 *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_GUIDX + +#define DEFINE_GUIDX(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_GUIDX(GUID_SysMouse, 0x6F1D2B60,0xD5A0,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUIDX(GUID_XAxis, 0xA36D02E0,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUIDX(GUID_YAxis, 0xA36D02E1,0xC9F3,0x11CF,0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00); +DEFINE_GUIDX(GUID_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[] = { + { &GUID_XAxis, FIELD_OFFSET(MYDATA, lX), DIDFT_AXIS | DIDFT_ANYINSTANCE, 0,}, + { &GUID_YAxis, FIELD_OFFSET(MYDATA, lY), DIDFT_AXIS | DIDFT_ANYINSTANCE, 0,}, + { &GUID_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 = IDirectInput_CreateDevice(g_pdi, GUID_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; + } +} + +/* +========================== +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; + + case DIMOFS_BUTTON3: + if (od.dwData & 0x80) + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE4, qtrue, 0, NULL ); + else + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE4, qfalse, 0, NULL ); + break; + + // needs DIRECTINPUT_VERSION >= 0x0700 to compile, which we seem to have, so... + // + case DIMOFS_BUTTON4: + if (od.dwData & 0x80) + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE5, qtrue, 0, NULL ); + else + Sys_QueEvent( od.dwTimeStamp, SE_KEY, A_MOUSE5, 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" ); +} + + +/* +=========== +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); + + 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(); +} + + +/* +=========== +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(); + } +} + +extern cvar_t *r_fullscreen; + +/* +================== +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 (r_fullscreen && r_fullscreen->value == 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_VariableValue( "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_VariableValue( "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 ); + } + Com_Memset( &s_midiInfo, 0, sizeof( s_midiInfo ) ); +} + diff --git a/codemp/win32/win_input.h b/codemp/win32/win_input.h new file mode 100644 index 0000000..56d47cc --- /dev/null +++ b/codemp/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/codemp/win32/win_input_console.cpp b/codemp/win32/win_input_console.cpp new file mode 100644 index 0000000..a08543e --- /dev/null +++ b/codemp/win32/win_input_console.cpp @@ -0,0 +1,648 @@ +// #include "../server/exe_headers.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" + + +static void HandleDebugJoystickPress(fakeAscii_t button); + +static bool _UIRunning = false; + +static bool IN_ControllerMustBePlugged(int controller); + +#ifdef _DEBUG +bool cheatPadEnabled = 1; +#else +bool cheatPadEnabled = 1; +#endif + +// Controller connection globals +static signed char uiControllerNotification = -1; +bool noControllersConnected = false; +bool wasPlugged[4]; + +PadInfo _padInfo; // gamepad thumbstick buffer + + + +//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) { + 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; + case A_JOY15: + return A_MOUSE1; +#ifdef _GAMECUBE + case A_JOY16: + return A_ESCAPE; + case A_JOY14: + return A_DELETE; +#else + case A_JOY14: + return A_ESCAPE; + case A_JOY16: + return A_DELETE; + // Arbitrary choice for X button - need it for passcodes. + case A_JOY13: + return A_BACKSPACE; +#endif + + //left and right trigger for scrolling + case A_JOY11: + return A_PAGE_UP; + case A_JOY12: + return A_PAGE_DOWN; + + // start and back button on xbox + case A_JOY1: + //JLF MPMOVED + return A_ESCAPE; + case A_JOY2: + case A_JOY4: + //JLF MPMOVED + return A_MOUSE1; + //return button; + + case A_JOY3: + return A_MOUSE1; + } + + 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); +void IN_CommonJoyPress(int controller, fakeAscii_t button, bool pressed) +{ + // 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(IN_GetMainController() == controller || _UIRunning) + { + // 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 ); + +#ifndef FINAL_BUILD + if (controller != 3 || !cheatPadEnabled) +#endif + Sys_QueEvent( 0, SE_KEY, _UIRunning ? UIJoy2Key(button) : button, pressed, 0, NULL ); + } + +/* + if (pressed) + { + G_DemoKeypress(); + } + + //Hacky! Skip the credits if start is pressed. + if(button == K_JOY4 && pressed) { + CG_SkipCredits(); + } +*/ + +#ifndef FINAL_BUILD + if (controller == 3 && pressed && cheatPadEnabled) { + HandleDebugJoystickPress(button); + return; + } +#endif + +/* + extern int player1ControllerId; + extern int player2ControllerId; + extern bool checkForPlayerControllers; + extern bool controllerUnplugged; + + + // If the game isn't started yet + if ((ClientManager::Shared().cls.cgameStarted == qfalse) && + (!controllerUnplugged)) + { + // and player1 doesnt have a controllerid, then assign client 1 to this controller + if (player1ControllerId == -1) + { + ClientManager::ActivateClient(0); + if (ClientManager::ActiveController() != controller) + ClientManager::SetActiveController(controller); + } + // player1 has a controller that is different then input recieved, and player2 doesnt have a controller and there are 2 clients + else if (player1ControllerId != controller && player2ControllerId == -1 && ClientManager::NumClients() > 1) + { + ClientManager::ActivateClient(1); + if (ClientManager::ActiveController() != controller) + ClientManager::SetActiveController(controller); + } + } + + if (ClientManager::ActivateByControllerId(controller)) + { + + #ifdef _XBOX + //Check the status of the white or black buttons. + if (button == K_JOY9) { + ClientManager::ActiveClient().whiteButtonDown = pressed; + } else if(button == K_JOY10) { + ClientManager::ActiveClient().blackButtonDown = pressed; + } + + //Ignore white/black button presses if inv/force/weap select is up. + //This is ugly. It basically says return if the UI isn't running, if + //we got a white or black button, and if the inv/force/weapon select is + //running. + if(!_UIRunning && + (button == K_JOY9 || + button == K_JOY10) && + (ClientManager::ActiveClient().cg.inventorySelectTime + + WEAPON_SELECT_TIME > ClientManager::ActiveClient().cg.time || + ClientManager::ActiveClient().cg.forcepowerSelectTime + + WEAPON_SELECT_TIME > ClientManager::ActiveClient().cg.time || + ClientManager::ActiveClient().cg.weaponSelectTime + + WEAPON_SELECT_TIME > ClientManager::ActiveClient().cg.time)) { + + if(!pressed) { + //And it just gets hackier! Is that a word? It is now! + //If we've released the button and it wasn't down too long... + if((button == K_JOY9 && + ClientManager::ActiveClient().whiteButtonHoldTime < + MAX_WB_HOLD_TIME) || + (button == K_JOY10 && + ClientManager::ActiveClient().whiteButtonHoldTime < + MAX_WB_HOLD_TIME)) { + //If we've already let the button press through previously, + //just send a release message. Otherwise send both a press + //and release. + if(ClientManager::ActiveClient().keys[button].down) { + Sys_QueEvent( 0, SE_KEY, button, false, 0, NULL ); + } else { + Sys_QueEvent( 0, SE_KEY, button, true, 0, NULL ); + Sys_QueEvent( 0, SE_KEY, button, false, 0, NULL ); + } + } + } + return; + } + #endif + + if (!_UIRunning) + { + Sys_QueEvent( 0, SE_KEY, button, pressed, 0, NULL ); + } + else + { +// int clientNum = ClientManager::ActiveClientNum(); + int clientNum = 0; // VVFIXME + int qL = uiQueueLen[clientNum]; + if(qL < 5) { + uiKeyQueue[clientNum][qL].button = UIJoy2Key(button); + uiKeyQueue[clientNum][qL].pressed = pressed; + uiQueueLen[clientNum]++; + } + //Sys_QueEvent(0, SE_KEY, UIJoy2Key(button), pressed, 0, NULL); + } +*/ +/* + } +*/ + +} + +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; + + // if the UI is running, then let all gamepad sticks work, else only main controller + if(_UIRunning) + Sys_QueEvent( 0, SE_MOUSE, _padInfo.joyInfo[1].x * 4.0f, _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; + + 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; + } + } +} + +/********* +IN_DisplayControllerUnplugged +*********/ +static void IN_DisplayControllerUnplugged(int controller) +{ + uiControllerNotification = controller; + + //TODO Add a call to the UI that draws a controller disconnected message + // on the screen. +// VM_Call( uivm, UI_CONTROLLER_UNPLUGGED, true, controller); +} + +/********* +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); +} + +/********* +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(!_UIRunning && controller == IN_GetMainController()) + { + return true; + } + + if(noControllersConnected) + { + return true; + } + + return false; +} + +/********* +IN_PadUnplugged +*********/ +void IN_PadUnplugged(int controller) +{ + if(wasPlugged[controller]) + { + Com_Printf("\tController %d unplugged\n",controller); + } + + if(IN_ControllerMustBePlugged(controller)) + { + //If UI isn't busy, inform it about controller loss. + if(uiControllerNotification == -1) + { + IN_DisplayControllerUnplugged(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)) + { + //If UI is dealing with this controller, tell it to stop. + if(uiControllerNotification == controller) + { + IN_ClearControllerUnplugged(); + } + } + wasPlugged[controller] = true; + noControllersConnected = false; +} + +/********* +IN_GetMainController +*********/ +int IN_GetMainController(void) +{ + return cls.mainGamepad; +} + +/********* +IN_SetMainController +*********/ +void IN_SetMainController(int id) +{ + cls.mainGamepad = id; +} + +/********* +IN_SetThumbStickConfig +Sets the thumbstick configuration value +*********/ +void IN_SetThumbStickConfig(int configValue) +{ + Cvar_Set("ui_thumbStickMode", va("%i", configValue)); +} + +/********* +IN_SetButtonConfig +Execs a button configuration script based on configValue +*********/ +void IN_SetButtonConfig(int configValue) +{ + // Set the cvar + Cvar_Set("ui_buttonMode", va("%i", configValue)); + + // Exec the script + char execString[40]; + sprintf (execString, "exec cfg\\buttonConfig%i.cfg\n", configValue); + Cbuf_ExecuteText (EXEC_NOW, execString); +} + +/********* +IN_SetDpadConfig +Execs a dpad configuration script based on configValue +*********/ +void IN_SetDpadConfig(int configValue) +{ + // Set the cvar + Cvar_Set("ui_dpadMode", va("%i", configValue)); + + // Exec the script + char execString[40]; + sprintf (execString, "exec cfg\\dpadConfig%i.cfg\n", configValue); + Cbuf_ExecuteText (EXEC_NOW, execString); +} + +/********************************************************** +* +* DEBUGGING CODE +* +**********************************************************/ +bool debugSoundOff; + +#ifndef FINAL_BUILD +static void HandleDebugJoystickPress(fakeAscii_t button) +{ + // Super hackalicious crap used below. Please remove this at some point. + static int curSaberSet = 0; + static int curPlayerSet = 0; + static short dpadmode = 0; + static short buttonmode = 0; + static short thumbmode = 0; + + switch(button) { + case A_JOY13: // Right pad up + Cbuf_ExecuteText(EXEC_APPEND, "give all\n"); + break; + case A_JOY16: // Right pad left + Cbuf_ExecuteText(EXEC_APPEND, "viewpos\n"); + break; + case A_JOY14: // Right pad right + Cbuf_ExecuteText(EXEC_APPEND, "noclip\n"); + break; + case A_JOY15: // Right pad down + Cbuf_ExecuteText(EXEC_APPEND, "god\n"); + break; + case A_JOY4: // Start + Cvar_SetValue("m_pitch", -Cvar_VariableValue("m_pitch")); + break; + case A_JOY1: // back + Cvar_SetValue("cl_autolevel", !Cvar_VariableIntegerValue("cl_autolevel")); + break; + case A_JOY2: // Left thumbstick + extern void Z_CompactStats(void); + Z_CompactStats(); + break; + case A_JOY12: // Upper right trigger + Cbuf_ExecuteText(EXEC_APPEND, "load current\n"); + break; + case A_JOY8: // Left pad left + thumbmode++; + if(thumbmode == 4) + { + thumbmode = 0; + } + IN_SetThumbStickConfig(thumbmode); + break; + case A_JOY6: // Left pad right + dpadmode++; + if(dpadmode == 4) + { + dpadmode = 0; + } + IN_SetDpadConfig(0); + break; + case A_JOY5: // Left pad up + buttonmode++; + if(buttonmode == 4) + { + buttonmode = 0; + } + IN_SetButtonConfig(buttonmode); + break; + case A_JOY7: // Left pad down + Cbuf_ExecuteText(EXEC_APPEND, "vid_restart\n"); + break; + case A_JOY11: // Upper left trigger + // VVFIXME : This is totally bootleg. The above loads current, because the + // current SG system writes out to "current" and then uses FS to move the file + // to the name that we specify. Which we don't support. But we can't write to + // "current" as the SG system doesn't allow it. So we write to some arbitary + // name, and our game ends up in current. + Cbuf_ExecuteText(EXEC_APPEND, "save foo\n"); +#if 0 // VVFIXME + extern cvar_t *cl_safezonemask; + if(cl_safezonemask) { + if(cl_safezonemask->integer) { + Cbuf_ExecuteText(EXEC_APPEND, "safezonemask 0\n"); + } else { + Cbuf_ExecuteText(EXEC_APPEND, "safezonemask 1\n"); + } + } +#endif + break; + case A_JOY9: // White button + // Hacky. Really hacky. No, hackier than that. + curSaberSet = (curSaberSet + 1) % 3; // Number of xsaber strings in config file + Cbuf_ExecuteText(EXEC_APPEND, va("vstr xsaber%d\n", curSaberSet)); + break; + case A_JOY10: // Black button + curPlayerSet = (curPlayerSet + 1) % 6; // Number of xplayer strings in config file + Cbuf_ExecuteText(EXEC_APPEND, va("vstr xplayer%d\n", curPlayerSet)); + break; + } +} + +#endif + diff --git a/codemp/win32/win_input_rumble.cpp b/codemp/win32/win_input_rumble.cpp new file mode 100644 index 0000000..afa1352 --- /dev/null +++ b/codemp/win32/win_input_rumble.cpp @@ -0,0 +1,705 @@ + +/* + * 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); + +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; + +/***** FIXME Some functions that would be found in a client manager *****/ +/***** BEGIN FILLER *****/ + +// Always return 0 because we have only one client (right now anyway) +int ActiveClientNum(void) +{ + return 0; +} + +// The active controller will always be number 0 for now +int ActiveController(void) +{ + return 0; +} +/***** END FILLER *****/ + +void IN_enableRumble( void ) +{ + if (ActiveClientNum() == 0) + { + Cvar_Set( "in_useRumble", "1"); + } + else + { + Cvar_Set( "in_useRumble2", "1"); + } +} + +void IN_disableRumble( void ) +{ + if (ActiveClientNum() == 0) + { + Cvar_Set( "in_useRumble", "0"); + } + else + { + Cvar_Set( "in_useRumble2", "0"); + } +} + +bool IN_usingRumble( void ) +{ + if (ActiveClientNum() == 0) + { + return Cvar_VariableIntegerValue( "in_useRumble"); + } + else + { + return Cvar_VariableIntegerValue( "in_useRumble2"); + } + + return true; +} + + +// 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; +} + +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 = IN_Time() + cmd; + + rumbleStatus[rumbleScripts[whichScript].controller].changed = true; + rumbleStatus[rumbleScripts[whichScript].controller].killed = false; +} + + + +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); +} + +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; +} + +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 +void IN_UpdateRumbleFromStates() +{ + //if (!IN_usingRumble()) return; +/*mb extern int G_ShouldBeRumbling(); + if (!G_ShouldBeRumbling()) + return; +*/ + int usingRumble[2]; + usingRumble[0] = Cvar_VariableIntegerValue("in_useRumble"); + usingRumble[1] = Cvar_VariableIntegerValue("in_useRumble2"); + + int i; + int value[MAX_RUMBLE_CONTROLLERS][2]; + int cur_time = IN_Time(); + + 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 ( !usingRumble[ActiveClientNum()] ) + { + IN_KillRumbleScript(i); + continue; + } +/*mb + if (!ClientManager::ActiveGentity() || !G_ActivePlayerNormal()) + { + IN_KillRumbleScript(i); + continue; + } +*/ + // Unset state so skip + if ( rumbleScripts[i].nextStateAt == 0) continue; + + // 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; + } +} + + + +/* +================== +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 ); + Cvar_Get("in_useRumble2", "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/codemp/win32/win_input_xbox.cpp b/codemp/win32/win_input_xbox.cpp new file mode 100644 index 0000000..e52ca0c --- /dev/null +++ b/codemp/win32/win_input_xbox.cpp @@ -0,0 +1,309 @@ +// 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(); + +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 + +========================================================================= +*/ +// 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) && 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}, + {XDEVICE_TYPE_MEMORY_UNIT, 1}, + {XDEVICE_TYPE_VOICE_MICROPHONE, 1}, + {XDEVICE_TYPE_VOICE_HEADPHONE, 1} + }; + + // 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_LEFT + 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(); +} + +/* +================== +IN_Frame + +Called every frame, even if not generating commands +================== +*/ +//extern int ignoreInputTime; +void IN_Frame (void) +{ + 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); + } + else + { + IN_CheckForNoControllers(); + } + + // 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/codemp/win32/win_local.h b/codemp/win32/win_local.h new file mode 100644 index 0000000..afac44d --- /dev/null +++ b/codemp/win32/win_local.h @@ -0,0 +1,84 @@ +// win_local.h: Win32-specific Quake3 header file + +#if defined (_MSC_VER) && (_MSC_VER >= 1200) +#pragma warning(disable : 4201) +#pragma warning( push ) +#endif +//#include +#include "../qcommon/platform.h" +#if defined (_MSC_VER) && (_MSC_VER >= 1200) +#pragma warning( pop ) +#endif + +#ifndef _XBOX +#define DIRECTINPUT_VERSION 0x0800 //[ 0x0300 | 0x0500 | 0x0700 | 0x0800 ] +#include +#include +#include +#include +#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); + +qboolean Sys_GetPacket ( netadr_t *net_from, msg_t *net_message ); + +// 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 ); +int SNDDMA_InitDS (); + +#ifndef _XBOX +typedef struct +{ + + HINSTANCE reflib_library; // Handle to refresh DLL + qboolean reflib_active; + + 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 ) + diff --git a/codemp/win32/win_main.cpp b/codemp/win32/win_main.cpp new file mode 100644 index 0000000..29f8ad4 --- /dev/null +++ b/codemp/win32/win_main.cpp @@ -0,0 +1,1609 @@ +// win_main.c +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../client/client.h" +#include "win_local.h" +#include "resource.h" +#include +#include +#include +#include +#include +#include +#include +#include "../qcommon/stringed_ingame.h" + +#define CD_BASEDIR "gamedata\\gamedata" +#define CD_EXE "jamp.exe" +#define CD_BASEDIR_LINUX "bin\\x86\\glibc-2.1" +#define CD_EXE_LINUX "jamp" +#define CD_VOLUME "JEDIACAD" +#define MEM_THRESHOLD 128*1024*1024 + +static char sys_cmdline[MAX_STRING_CHARS]; + +// enable this for executable checksumming +#ifdef FINAL_BUILD +#define SPANK_MONKEYS +#endif +static int sys_monkeySpank; +static int sys_checksum; + + +/* +================== +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_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; + 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); + return Com_BlockChecksum( f1_ptr, l ); +} + +/* +================== +Sys_MonkeyShouldBeSpanked +================== +*/ +int Sys_MonkeyShouldBeSpanked( void ) { + return sys_monkeySpank; +} + +/* +================== +Sys_CodeInMemoryChecksum +================== +*/ +#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr) + (addValue) ) + +int Sys_CodeInMemoryChecksum( void *codeBase ) { + PIMAGE_DOS_HEADER dosHeader; + PIMAGE_NT_HEADERS pNTHeader; + PIMAGE_SECTION_HEADER section; + + dosHeader = (PIMAGE_DOS_HEADER)codeBase; + pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader, dosHeader->e_lfanew ); + + // First, verify that the e_lfanew field gave us a reasonable + // pointer, then verify the PE signature. + if ( IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) || + pNTHeader->Signature != IMAGE_NT_SIGNATURE ) + { + //printf("Unhandled EXE type, or invalid .EXE\n"); + return 0; + } + // first section oughta be the code section + section = (PIMAGE_SECTION_HEADER)(pNTHeader+1); + /* + // the name of the code section should be .text + if ( Q_stricmp( section->Name, ".text" ) ) { + return 0; + } + */ + + return Com_BlockChecksum( ((byte *) codeBase) + section->VirtualAddress, section->SizeOfRawData ); +} + +/* +================== +Sys_ChecksumExe +================== +*/ + +// make sure this string is unique in the executable +// 01234567890123 +byte *exeChecksumId = (unsigned char *)"q3monkeyid\0\0\0\0"; + +void Sys_ChecksumExe( void *codeBase ) { + TCHAR szPathOrig[_MAX_PATH], szPathClone[_MAX_PATH]; + STARTUPINFO si; + TCHAR szCmdLine[512]; + HANDLE hfile, hProcessOrig; + PROCESS_INFORMATION pi; + int l, i, n; + FILE *f; + byte *buf, *ptr; + + // Is this the original EXE or the clone EXE? + if ( Q_stricmp(__argv[1], "monkey") ) { + // Original EXE: Spawn clone EXE to delete this EXE + + GetModuleFileName(NULL, szPathOrig, _MAX_PATH); + GetTempPath(_MAX_PATH, szPathClone); + GetTempFileName(szPathClone, __TEXT("Del"), 0, szPathClone); + CopyFile(szPathOrig, szPathClone, FALSE); + + // Open the clone EXE using FILE_FLAG_DELETE_ON_CLOSE + hfile = CreateFile(szPathClone, 0, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); + // Spawn the clone EXE passing it our EXE's process handle + // and the full path name to the original EXE file. + hProcessOrig = OpenProcess(SYNCHRONIZE, TRUE, + GetCurrentProcessId()); + wsprintf(szCmdLine, __TEXT("%s monkey %d %d \"%s\""), szPathClone, + sys_checksum, hProcessOrig, szPathOrig); + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + CreateProcess(NULL, szCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); + CloseHandle(hProcessOrig); + CloseHandle(hfile); + } else { + // Clone EXE: When original EXE terminates, overwrite it with a new one + sys_checksum = atoi( __argv[2] ); + hProcessOrig = (HANDLE) atoi( __argv[3] ); + WaitForSingleObject(hProcessOrig, INFINITE); + CloseHandle(hProcessOrig); + // open the original executable + f = fopen( __argv[4], "rb" ); + if ( !f ) { + return; + } + fseek (f, 0, SEEK_END); + l = ftell (f); + fseek (f, 0, SEEK_SET); + buf = (unsigned char *)malloc(l); + if ( fread(buf, l, 1, f) != 1 ) { + return; + } + fclose(f); + // search for the exe name string, nice brute force + n = strlen((const char *)exeChecksumId); + for ( i = 0; i < l; i++ ) { + if ( !Q_strncmp((const char *)(buf + i), (const char *)exeChecksumId, n) ) { + break; + } + } + if ( i >= l ) { + return; + } + ptr = buf + i; + // write checksum into exe memory image + ptr[0] = (sys_checksum >> 24) & 0xFF; + ptr[1] = (sys_checksum >> 16) & 0xFF; + ptr[2] = (sys_checksum >> 8) & 0xFF; + ptr[3] = (sys_checksum >> 0) & 0xFF; + ptr[4] = ptr[5] = ptr[6] = ptr[7] = ptr[8] = ptr[9] = 0; + // write out new exe with checksum + f = fopen( __argv[4], "wb" ); + if ( !f ) { + return; + } + if ( fwrite(buf, l, 1, f) != 1 ) { + return; + } + fclose(f); + free(buf); + // The system will delete the clone EXE automatically + // because it was opened with FILE_FLAG_DELETE_ON_CLOSE + } + // + exit(0); +} + +/* +================== +Sys_VerifyCodeChecksum +================== +*/ +void Sys_VerifyCodeChecksum( void *codeBase ) { + // NOTE: should not checksum code in debug mode because the memory image changes + // as soon as you set a break point! +#if defined(SPANK_MONKEYS) && !defined(_DEBUG) + int exeChecksum; + + // if the checksum is not yet stored in the executable + if ( exeChecksumId[4] != 0 ) { + // spawn another process that will replace this executable with one that has a checksum + Sys_ChecksumExe( codeBase ); + return; + } + + exeChecksum = (exeChecksumId[0] << 24) | (exeChecksumId[1] << 16) | (exeChecksumId[2] << 8) | exeChecksumId[3]; + if ( exeChecksum != sys_checksum ) { + sys_monkeySpank = qtrue; + } +#endif +} + +/* +================== +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 + +void Sys_ListFilteredFiles( const char *basedir, char *subdirs, char *filter, char **psList, int *numfiles ) { + char search[MAX_OSPATH], newsubdirs[MAX_OSPATH]; + char filename[MAX_OSPATH]; + int findhandle; + struct _finddata_t findinfo; + + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + return; + } + + if (strlen(subdirs)) { + Com_sprintf( search, sizeof(search), "%s\\%s\\*", basedir, subdirs ); + } + else { + Com_sprintf( search, sizeof(search), "%s\\*", basedir ); + } + + findhandle = _findfirst (search, &findinfo); + if (findhandle == -1) { + return; + } + + do { + if (findinfo.attrib & _A_SUBDIR) { + if (Q_stricmp(findinfo.name, ".") && Q_stricmp(findinfo.name, "..")) { + if (strlen(subdirs)) { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s\\%s", subdirs, findinfo.name); + } + else { + Com_sprintf( newsubdirs, sizeof(newsubdirs), "%s", findinfo.name); + } + Sys_ListFilteredFiles( basedir, newsubdirs, filter, psList, numfiles ); + } + } + if ( *numfiles >= MAX_FOUND_FILES - 1 ) { + break; + } + Com_sprintf( filename, sizeof(filename), "%s\\%s", subdirs, findinfo.name ); + if (!Com_FilterPath( filter, filename, qfalse )) + continue; + psList[ *numfiles ] = CopyString( filename ); + (*numfiles)++; + } while ( _findnext (findhandle, &findinfo) != -1 ); + + _findclose (findhandle); +} + +static qboolean strgtr(const char *s0, const char *s1) { + int l0, l1, i; + + l0 = strlen(s0); + l1 = strlen(s1); + + if (l1 s0[i]) { + return qtrue; + } + if (s1[i] < s0[i]) { + return qfalse; + } + } + return qfalse; +} + +char **Sys_ListFiles( const char *directory, const char *extension, char *filter, 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 (filter) { + + nfiles = 0; + Sys_ListFilteredFiles( directory, "", filter, list, &nfiles ); + + list[ nfiles ] = 0; + *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; + } + + 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_FILESYS ); + for ( i = 0 ; i < nfiles ; i++ ) { + listCopy[i] = list[i]; + } + listCopy[i] = NULL; + + do { + flag = 0; + for(i=1; i (5 * 60000)) && !Cvar_VariableIntegerValue( "dedicated" ) +// && !Cvar_VariableIntegerValue( "com_blindlyLoadDLLs" ) ) { + if (0) { + if (FS_FileExists(filename)) { + lastWarning = timestamp; + ret = MessageBoxEx( NULL, "You are about to load a .DLL executable that\n" + "has not been verified for use with Quake III Arena.\n" + "This type of file can compromise the security of\n" + "your computer.\n\n" + "Select 'OK' if you choose to load it anyway.", + "Security Warning", MB_OKCANCEL | MB_ICONEXCLAMATION | MB_DEFBUTTON2 | MB_TOPMOST | MB_SETFOREGROUND, + MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ) ); + if( ret != IDOK ) { + return NULL; + } + } + } +#endif + + if (!Sys_UnpackDLL(filename)) + { + return NULL; + } + +// rjr disable for final release #ifndef NDEBUG + libHandle = LoadLibrary( filename ); + if ( !libHandle ) { +//#endif + basepath = Cvar_VariableString( "fs_basepath" ); + cdpath = Cvar_VariableString( "fs_cdpath" ); + gamedir = Cvar_VariableString( "fs_game" ); + + fn = FS_BuildOSPath( basepath, gamedir, filename ); + libHandle = LoadLibrary( fn ); + + if ( !libHandle ) { + if( cdpath[0] ) { + fn = FS_BuildOSPath( cdpath, gamedir, filename ); + libHandle = LoadLibrary( fn ); + } + + if ( !libHandle ) { + return NULL; + } + } +//#ifndef NDEBUG + } +//#endif + + dllEntry = ( void (QDECL *)( int (QDECL *)( int, ... ) ) )GetProcAddress( libHandle, "dllEntry" ); + *entryPoint = (int (QDECL *)(int,...))GetProcAddress( libHandle, "vmMain" ); + if ( !*entryPoint || !dllEntry ) { + FreeLibrary( libHandle ); + return NULL; + } + dllEntry( systemcalls ); + + return libHandle; +} + + +/* +======================================================================== + +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 { + fileHandle_t file; + byte *buffer; + qboolean eof; + qboolean active; + int bufferSize; + int streamPosition; // next byte to be returned by Sys_StreamRead + int threadPosition; // next byte to be read from file +} streamsIO_t; + +typedef struct { + HANDLE threadHandle; + int threadId; + CRITICAL_SECTION crit; + streamsIO_t sIO[MAX_FILE_HANDLES]; +} 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, i; + + while (1) { + Sleep( 10 ); +// EnterCriticalSection (&stream.crit); + + for (i=1;i 0 ) { + available = stream.sIO[f].threadPosition - stream.sIO[f].streamPosition; + if ( !available ) { + if ( stream.sIO[f].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; + } + + EnterCriticalSection( &stream.crit ); + + bufferPoint = stream.sIO[f].streamPosition % stream.sIO[f].bufferSize; + bufferCount = stream.sIO[f].bufferSize - bufferPoint; + + copy = available < bufferCount ? available : bufferCount; + if ( copy > remaining ) { + copy = remaining; + } + memcpy( dest, stream.sIO[f].buffer + bufferPoint, copy ); + stream.sIO[f].streamPosition += copy; + dest += copy; + remaining -= copy; + + LeaveCriticalSection( &stream.crit ); + } + + 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.sIO[f].streamPosition = 0; + stream.sIO[f].threadPosition = 0; + stream.sIO[f].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; + netadr_t adr; + + // 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 ); + Q_strncpyz( b, s, len ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + // 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 + // the readcount stepahead is for SOCKS support + len = sizeof( netadr_t ) + netmsg.cursize - netmsg.readcount; + buf = (netadr_t *)Z_Malloc( len, TAG_EVENT, qtrue ); + *buf = adr; + memcpy( buf+1, &netmsg.data[netmsg.readcount], netmsg.cursize - netmsg.readcount ); + 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 = timeGetTime(); + + return ev; +} + +//================================================================ + +/* +================= +Sys_In_Restart_f + +Restart the input subsystem +================= +*/ +void Sys_In_Restart_f( void ) { + IN_Shutdown(); + IN_Init(); +} + + +/* +================= +Sys_Net_Restart_f + +Restart the network subsystem +================= +*/ +void Sys_Net_Restart_f( void ) { + NET_Restart(); +} + +static bool Sys_IsExpired() +{ +#if 0 +// sec min Hr Day Mon Yr + struct tm t_valid_start = { 0, 0, 8, 5, 8, 103 }; //zero based months! +// sec min Hr Day Mon Yr + struct tm t_valid_end = { 0, 0, 20, 13, 8, 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 + +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); + Cmd_AddCommand ("net_restart", Sys_Net_Restart_f); + + 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() ); + Cvar_SetValue( "sys_cpuspeed", Sys_GetCPUSpeed() ); + Cvar_SetValue( "sys_memory", Sys_GetPhysicalMemory() ); + + IN_Init(); // FIXME: not in dedicated? +} + +// do a quick mem test to check for any potential future mem problems... +// +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) { +// int startTime, endTime; + + // should never get a previous instance in Win32 + if ( hPrevInstance ) { + return 0; + } + + sys_checksum = Sys_CodeInMemoryChecksum( hInstance ); + Sys_VerifyCodeChecksum( hInstance ); + + 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 ); + +#if !defined(DEDICATED) + QuickMemTest(); +#endif + + NET_Init(); + + // hide the early console since we've reached the point where we + // have a working graphics subsystems + if ( !com_dedicated->integer && !com_viewlog->integer ) { + Sys_ShowConsole( 0, qfalse ); + } + +#ifdef _DEBUG + if ( sys_monkeySpank ) { + Cvar_Set("cl_trn", "666"); + } +#endif + + // main game loop + while( 1 ) { + // if not running as a game client, sleep a bit + if ( g_wv.isMinimized || ( com_dedicated && com_dedicated->integer ) ) { + 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/codemp/win32/win_main_common.cpp b/codemp/win32/win_main_common.cpp new file mode 100644 index 0000000..cab696a --- /dev/null +++ b/codemp/win32/win_main_common.cpp @@ -0,0 +1,331 @@ +// win_main.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/codemp/win32/win_main_console.cpp b/codemp/win32/win_main_console.cpp new file mode 100644 index 0000000..147fa5e --- /dev/null +++ b/codemp/win32/win_main_console.cpp @@ -0,0 +1,615 @@ +#include "../qcommon/qcommon.h" +#include "../client/client.h" +#include "win_local.h" +#include "resource.h" +#include +#include +#include "../game/g_public.h" +#include "../xbox/XBLive.h" + +#ifdef _XBOX +#include +#define NEWDECL __cdecl + +#ifndef FINAL_BUILD +#include "dbg_console_xbox.h" +#endif + +#endif + +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); +} + + + + +char *Sys_Cwd( void ) { + static char cwd[MAX_OSPATH]; + +#ifdef _XBOX + strcpy(cwd, "d:"); +#endif + +#ifdef _GAMECUBE + strcpy(cwd, "."); +#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 + +================ +*/ +#define MAX_POLL_RATE 15 +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; + netadr_t adr; + + for (int poll = 0; poll < MAX_POLL_RATE; ++poll) + { + 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 + // the readcount stepahead is for SOCKS support + len = sizeof( netadr_t ) + netmsg.cursize - netmsg.readcount; + //buf = (netadr_t *)GG_Malloc( len, MemoryBlock::kEventTag, qtrue ); + buf = (netadr_t *) Z_Malloc(len, TAG_EVENT, qfalse, 4); + *buf = adr; + memcpy( buf+1, &netmsg.data[netmsg.readcount], netmsg.cursize - netmsg.readcount ); + Sys_QueEvent( 0, SE_PACKET, 0, 0, len, buf ); + } + else + { + // Bail out if there's no more data + break; + } + } + +#if 0 // Removed as in SOF2 + // return if we have data + if ( eventHead > eventTail ) { + eventTail++; + return eventQue[ ( eventTail - 1 ) & MASK_QUED_EVENTS ]; + } +#endif + + // 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 + +===================== +*/ +#define LAUNCH_MAGIC "J3D1" +void Sys_Reboot( const char *reason ) +{ + LAUNCH_DATA ld; + const char *path = NULL; + + memset( &ld, 0, sizeof(ld) ); + + if (!Q_stricmp(reason, "new_account")) + { + PLD_LAUNCH_DASHBOARD pDash = (PLD_LAUNCH_DASHBOARD) &ld; + pDash->dwReason = XLD_LAUNCH_DASHBOARD_NEW_ACCOUNT_SIGNUP; + path = NULL; + } + else if (!Q_stricmp(reason, "net_config")) + { + PLD_LAUNCH_DASHBOARD pDash = (PLD_LAUNCH_DASHBOARD) &ld; + pDash->dwReason = XLD_LAUNCH_DASHBOARD_NETWORK_CONFIGURATION; + path = NULL; + } + else if (!Q_stricmp(reason, "manage_account")) + { + PLD_LAUNCH_DASHBOARD pDash = (PLD_LAUNCH_DASHBOARD) &ld; + pDash->dwReason = XLD_LAUNCH_DASHBOARD_ACCOUNT_MANAGEMENT; + path = NULL; + } + else if (!Q_stricmp(reason, "singleplayer")) + { + path = "d:\\default.xbe"; + strcpy((char *)&ld.Data[0], LAUNCH_MAGIC); + } + 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(); + + XLaunchNewImage(path, &ld); + + // This function should not return! + Com_Error( ERR_FATAL, "ERROR: XLaunchNewImage returned\n" ); +} + + +/* +================== +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(); + + Win_Init(); + Com_Init( "" ); + + //Start sound early. The STL inside will allocate memory and we don't + //want that memory in the middle of the zone. + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; + S_BeginRegistration(); + } + + NET_Init(); + + // main game loop + while( 1 ) { + IN_Frame(); + Com_Frame(); + + // Do any XBL stuff +// XBL_Tick(); + + // 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 + if (strstr(directory, "d:\\base\\")) + directory += 8; + + 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/codemp/win32/win_net.cpp b/codemp/win32/win_net.cpp new file mode 100644 index 0000000..03a45d5 --- /dev/null +++ b/codemp/win32/win_net.cpp @@ -0,0 +1,1222 @@ +// net_wins.c +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "win_local.h" + +static WSADATA winsockdata; +static qboolean winsockInitialized = qfalse; +static qboolean usingSocks = qfalse; +static qboolean networkingEnabled = qfalse; + +static cvar_t *net_noudp; +static cvar_t *net_noipx; + +static cvar_t *net_forcenonlocal; + +static cvar_t *net_socksEnabled; +static cvar_t *net_socksServer; +static cvar_t *net_socksPort; +static cvar_t *net_socksUsername; +static cvar_t *net_socksPassword; +static struct sockaddr socksRelayAddr; + +#ifdef _XBOX +SOCKET v_socket = INVALID_SOCKET; +#endif +static SOCKET ip_socket = INVALID_SOCKET; +static SOCKET socks_socket = INVALID_SOCKET; +static SOCKET ipx_socket = INVALID_SOCKET; + +#define MAX_IPS 16 +static int numIP; +static byte localIP[MAX_IPS][4]; + +//============================================================================= + + +/* +==================== +NET_ErrorString +==================== +*/ +char *NET_ErrorString( void ) { + int code; + + code = WSAGetLastError(); + switch( code ) { + case WSAEINTR: return "WSAEINTR"; + case WSAEBADF: return "WSAEBADF"; + case WSAEACCES: return "WSAEACCES"; + case WSAEDISCON: return "WSAEDISCON"; + case WSAEFAULT: return "WSAEFAULT"; + case WSAEINVAL: return "WSAEINVAL"; + case WSAEMFILE: return "WSAEMFILE"; + case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: return "WSAEINPROGRESS"; + case WSAEALREADY: return "WSAEALREADY"; + case WSAENOTSOCK: return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: return "WSAENETDOWN"; + case WSAENETUNREACH: return "WSAENETUNREACH"; + case WSAENETRESET: return "WSAENETRESET"; + case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR"; + case WSAECONNRESET: return "WSAECONNRESET"; + case WSAENOBUFS: return "WSAENOBUFS"; + case WSAEISCONN: return "WSAEISCONN"; + case WSAENOTCONN: return "WSAENOTCONN"; + case WSAESHUTDOWN: return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: return "WSAETIMEDOUT"; + case WSAECONNREFUSED: return "WSAECONNREFUSED"; + case WSAELOOP: return "WSAELOOP"; + case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; + case WSASYSNOTREADY: return "WSASYSNOTREADY"; + case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; + case WSANOTINITIALISED: return "WSANOTINITIALISED"; + case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; + case WSATRY_AGAIN: return "WSATRY_AGAIN"; + case WSANO_RECOVERY: return "WSANO_RECOVERY"; + case WSANO_DATA: return "WSANO_DATA"; + case WSAEHOSTUNREACH: return "WSAEHOSTUNREACH"; + default: return "NO ERROR"; + } +} + +void NetadrToSockadr( netadr_t *a, struct sockaddr *s ) { + memset( s, 0, sizeof(*s) ); + + if( a->type == NA_BROADCAST ) { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_port = a->port; + ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST; + } + else if( a->type == NA_IP ) { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip; + ((struct sockaddr_in *)s)->sin_port = a->port; + } +#ifndef _XBOX + else if( a->type == NA_IPX ) { + ((struct sockaddr_ipx *)s)->sa_family = AF_IPX; + memcpy( ((struct sockaddr_ipx *)s)->sa_netnum, &a->ipx[0], 4 ); + memcpy( ((struct sockaddr_ipx *)s)->sa_nodenum, &a->ipx[4], 6 ); + ((struct sockaddr_ipx *)s)->sa_socket = a->port; + } + else if( a->type == NA_BROADCAST_IPX ) { + ((struct sockaddr_ipx *)s)->sa_family = AF_IPX; + memset( ((struct sockaddr_ipx *)s)->sa_netnum, 0, 4 ); + memset( ((struct sockaddr_ipx *)s)->sa_nodenum, 0xff, 6 ); + ((struct sockaddr_ipx *)s)->sa_socket = a->port; + } +#endif //_XBOX +} + + +void SockadrToNetadr( struct sockaddr *s, netadr_t *a ) { + if (s->sa_family == AF_INET) { + a->type = NA_IP; + *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + a->port = ((struct sockaddr_in *)s)->sin_port; + } +#ifndef _XBOX + else if( s->sa_family == AF_IPX ) { + a->type = NA_IPX; + memcpy( &a->ipx[0], ((struct sockaddr_ipx *)s)->sa_netnum, 4 ); + memcpy( &a->ipx[4], ((struct sockaddr_ipx *)s)->sa_nodenum, 6 ); + a->port = ((struct sockaddr_ipx *)s)->sa_socket; + } +#endif //_XBOX +} + + +/* +============= +Sys_StringToAdr + +idnewt +192.246.40.70 +12121212.121212121212 +============= +*/ +#define DO(src,dest) \ + copy[0] = s[src]; \ + copy[1] = s[src + 1]; \ + sscanf (copy, "%x", &val); \ + ((struct sockaddr_ipx *)sadr)->dest = val + + +qboolean Sys_StringToSockaddr( const char *s, struct sockaddr *sadr ) +{ +#ifndef _XBOX + struct hostent *h; + int val; + char copy[MAX_STRING_CHARS]; +#endif + + + memset( sadr, 0, sizeof( *sadr ) ); + + // check for an IPX address + if( ( strlen( s ) == 21 ) && ( s[8] == '.' ) ) + { +#ifdef _XBOX + assert(!"IPX not supported on XBox"); +#else + ((struct sockaddr_ipx *)sadr)->sa_family = AF_IPX; + ((struct sockaddr_ipx *)sadr)->sa_socket = 0; + copy[2] = 0; + DO(0, sa_netnum[0]); + DO(2, sa_netnum[1]); + DO(4, sa_netnum[2]); + DO(6, sa_netnum[3]); + DO(9, sa_nodenum[0]); + DO(11, sa_nodenum[1]); + DO(13, sa_nodenum[2]); + DO(15, sa_nodenum[3]); + DO(17, sa_nodenum[4]); + DO(19, sa_nodenum[5]); +#endif + } + else + { + ((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 + { +#ifdef _XBOX + assert(!"gethostbyname() - unsupported on XBox"); +#else + if( ( h = gethostbyname( s ) ) == 0 ) { // NOT SUPPORTED ON XBOX! + return (qboolean)0; + + } + *(int *)&((struct sockaddr_in *)sadr)->sin_addr = *(int *)h->h_addr_list[0]; +#endif + } + } + + return qtrue; +} + +#undef DO + +/* +============= +Sys_StringToAdr + +idnewt +192.246.40.70 +============= +*/ +qboolean Sys_StringToAdr( const char *s, netadr_t *a ) { + struct sockaddr sadr; + + if ( !Sys_StringToSockaddr( s, &sadr ) ) { + return qfalse; + } + + SockadrToNetadr( &sadr, a ); + return qtrue; +} + +//============================================================================= + +/* +================== +Sys_GetPacket + +Never called by the game logic, just the system event queing +================== +*/ +int recvfromCount; + +qboolean Sys_GetPacket( netadr_t *net_from, msg_t *net_message ) { + int ret; + struct sockaddr from; + int fromlen; + SOCKET 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 == INVALID_SOCKET) { + continue; + } + + fromlen = sizeof(from); + recvfromCount++; // performance check + ret = recvfrom( net_socket, (char *)net_message->data, net_message->maxsize, 0, (struct sockaddr *)&from, &fromlen ); + + if (ret == SOCKET_ERROR) + { + err = WSAGetLastError(); + + if( err == WSAEWOULDBLOCK || err == WSAECONNRESET ) { + continue; + } + Com_Printf( "NET_GetPacket: %s\n", NET_ErrorString() ); + continue; + } + +#ifdef _XBOX + // VVFIXME - SOF2 calls into XBL class + // XBL_RcvdDataPacket(net_message->cursize); +#endif + + if ( net_socket == ip_socket ) { + memset( ((struct sockaddr_in *)&from)->sin_zero, 0, 8 ); + } + + if ( usingSocks && net_socket == ip_socket && memcmp( &from, &socksRelayAddr, fromlen ) == 0 ) { + if ( ret < 10 || net_message->data[0] != 0 || net_message->data[1] != 0 || net_message->data[2] != 0 || net_message->data[3] != 1 ) { + continue; + } + net_from->type = NA_IP; + net_from->ip[0] = net_message->data[4]; + net_from->ip[1] = net_message->data[5]; + net_from->ip[2] = net_message->data[6]; + net_from->ip[3] = net_message->data[7]; + net_from->port = *(short *)&net_message->data[8]; + net_message->readcount = 10; + } + else { + SockadrToNetadr( &from, net_from ); + net_message->readcount = 0; + } + + 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; +} + +//============================================================================= + +#ifdef _XBOX +/* +================== +Sys_SendVoicePacket +================== +*/ +void Sys_SendVoicePacket( int length, const void *data, netadr_t to ) { + int ret; + struct sockaddr addr; + + // check for valid packet intentions (direct send or broadcast) + // + if( to.type != NA_IP && to.type != NA_BROADCAST ) + { + Com_Error( ERR_FATAL, "Sys_SendVoicePacket: bad address type" ); + return; + } + + // check we have our voice socket set up + // + if( v_socket == INVALID_SOCKET) { + return; + } + + NetadrToSockadr( &to, &addr ); +#ifdef SOF2_METRICS + gXBL_NumberVoicePacketsSent++; + gXBL_SizeVoicePacketsSent += length; +#endif + /*if( usingSocks && to.type == NA_IP ) { + vsocksBuf[0] = 0; // reserved + vsocksBuf[1] = 0; + vsocksBuf[2] = 0; // fragment (not fragmented) + vsocksBuf[3] = 1; // address type: IPV4 + *(int *)&vsocksBuf[4] = ((struct sockaddr_in *)&addr)->sin_addr.s_addr; + *(short *)&vsocksBuf[8] = ((struct sockaddr_in *)&addr)->sin_port; + memcpy( &vsocksBuf[10], data, length ); + ret = sendto( v_socket, vsocksBuf, length+10, 0, &socksRelayAddr, sizeof(socksRelayAddr) ); + } + else {*/ + ret = sendto( v_socket, (const char *)data, length, 0, &addr, sizeof(addr) ); + //} + + if( ret == SOCKET_ERROR ) { + int err = WSAGetLastError(); + + // wouldblock is silent + if( err == WSAEWOULDBLOCK ) { + return; + } + + // some PPP links do not allow broadcasts and return an error + if( ( err == WSAEADDRNOTAVAIL ) && ( ( to.type == NA_BROADCAST ) || ( to.type == NA_BROADCAST_IPX ) ) ) { + return; + } + + Com_DPrintf( "NET_SendVoicePacket: %s\n", NET_ErrorString() ); + } +} +#endif + +static char socksBuf[4096]; + +/* +================== +Sys_SendPacket +================== +*/ +void Sys_SendPacket( int length, const void *data, netadr_t to ) { + int ret; + struct sockaddr addr; + SOCKET net_socket; + +#ifdef _XBOX + // VVFIXME - SOF2 calls into XBL code + // XBL_SentDataPacket( length ); +#endif + + 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 ) { +#ifdef _XBOX + return; +#else + net_socket = ipx_socket; +#endif + } + else if( to.type == NA_BROADCAST_IPX ) { +#ifdef _XBOX + return; +#else + net_socket = ipx_socket; +#endif + } + else { + Com_Error( ERR_FATAL, "Sys_SendPacket: bad address type" ); + return; + } + + if( net_socket == INVALID_SOCKET) { + return; + } + + NetadrToSockadr( &to, &addr ); + +#ifndef _XBOX + if( usingSocks && to.type == NA_IP ) { + socksBuf[0] = 0; // reserved + socksBuf[1] = 0; + socksBuf[2] = 0; // fragment (not fragmented) + socksBuf[3] = 1; // address type: IPV4 + *(int *)&socksBuf[4] = ((struct sockaddr_in *)&addr)->sin_addr.s_addr; + *(short *)&socksBuf[8] = ((struct sockaddr_in *)&addr)->sin_port; + memcpy( &socksBuf[10], data, length ); + ret = sendto( net_socket, socksBuf, length+10, 0, &socksRelayAddr, sizeof(socksRelayAddr) ); + } + else { +#endif + ret = sendto( net_socket, (const char *)data, length, 0, &addr, sizeof(addr) ); +#ifndef _XBOX + } +#endif + if( ret == SOCKET_ERROR ) { + int err = WSAGetLastError(); + + // wouldblock is silent + if( err == WSAEWOULDBLOCK ) { + return; + } + + // some PPP links do not allow broadcasts and return an error + if( ( err == WSAEADDRNOTAVAIL ) && ( ( to.type == NA_BROADCAST ) || ( to.type == NA_BROADCAST_IPX ) ) ) { + return; + } + + Com_Printf( "NET_SendPacket: %s\n", NET_ErrorString() ); + } +} + + +//============================================================================= + + +/* +================== +Sys_IsLANAddress + +LAN clients will have their rate var ignored +================== +*/ +qboolean Sys_IsLANAddress( netadr_t adr ) { + int i; + + if (!net_forcenonlocal) + { + net_forcenonlocal = Cvar_Get( "net_forcenonlocal", "0", 0 ); + } + + if (net_forcenonlocal && net_forcenonlocal->integer) + { + return qfalse; + } + + if( adr.type == NA_LOOPBACK ) { + return qtrue; + } + + if( adr.type == NA_IPX ) { + return qtrue; + } + + if( adr.type != NA_IP ) { + return qfalse; + } + + // choose which comparison to use based on the class of the address being tested + // any local adresses of a different class than the address being tested will fail based on the first byte + + if( adr.ip[0] == 127 && adr.ip[1] == 0 && adr.ip[2] == 0 && adr.ip[3] == 1 ) { + return qtrue; + } + + if ( (adr.ip[0] == 192 && adr.ip[1] == 168) || + (adr.ip[0] == 10 && adr.ip[1] == 100) || + (adr.ip[0] == 172 && adr.ip[1] == 16) ) + { + return qtrue; + } + + /* + // Class A + if( (adr.ip[0] & 0x80) == 0x00 ) { + for ( i = 0 ; i < numIP ; i++ ) { + if( adr.ip[0] == localIP[i][0] ) { + return qtrue; + } + } + // the RFC1918 class a block will pass the above test + return qfalse; + } + + // Class B + if( (adr.ip[0] & 0xc0) == 0x80 ) { + for ( i = 0 ; i < numIP ; i++ ) { + if( adr.ip[0] == localIP[i][0] && adr.ip[1] == localIP[i][1] ) { + return qtrue; + } + // also check against the RFC1918 class b blocks + if( adr.ip[0] == 172 && localIP[i][0] == 172 && (adr.ip[1] & 0xf0) == 16 && (localIP[i][1] & 0xf0) == 16 ) { + return qtrue; + } + } + return qfalse; + } + */ + //we only look at class C since ISPs and Universities are using class A but we don't want to consider them on the same LAN. + + // Class C + for ( i = 0 ; i < numIP ; i++ ) { + if( adr.ip[0] == localIP[i][0] && adr.ip[1] == localIP[i][1] && adr.ip[2] == localIP[i][2] ) { + return qtrue; + } + + //check for both on a local lan type thing + if( adr.ip[0] == 10 && localIP[i][0] == 10 ) + { + return qtrue; + } + + // also check against the RFC1918 class c blocks +// if( adr.ip[0] == 192 && localIP[i][0] == 192 && adr.ip[1] == 168 && localIP[i][1] == 168 ) { +// return qtrue; +// } + } + return qfalse; +} + +/* +================== +Sys_ShowIP +================== +*/ +void Sys_ShowIP(void) { + int i; + + for (i = 0; i < numIP; i++) { + Com_Printf( "IP: %i.%i.%i.%i\n", localIP[i][0], localIP[i][1], localIP[i][2], localIP[i][3] ); + } +} + + +//============================================================================= + + +/* +==================== +NET_IPSocket +==================== +*/ +int NET_IPSocket( char *net_interface, int port ) { + SOCKET newsocket; + struct sockaddr_in address; + qboolean _true = qtrue; + int i = 1; + int err; + + 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( AF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) { + err = WSAGetLastError(); + if( err != WSAEAFNOSUPPORT ) { + Com_Printf( "WARNING: UDP_OpenSocket: socket: %s\n", NET_ErrorString() ); + } + return INVALID_SOCKET; + } + + // make it non-blocking + if( ioctlsocket( newsocket, FIONBIO, (unsigned long *)&_true ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: UDP_OpenSocket: ioctl FIONBIO: %s\n", NET_ErrorString() ); + return INVALID_SOCKET; + } + + // make it broadcast capable + if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i) ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: UDP_OpenSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString() ); + return INVALID_SOCKET; + } + + if( !net_interface || !net_interface[0] || !Q_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, (const struct sockaddr *)&address, sizeof(address) ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: UDP_OpenSocket: bind: %s\n", NET_ErrorString() ); + closesocket( newsocket ); + return INVALID_SOCKET; + } + + return newsocket; +} + + +/* +==================== +NET_OpenSocks +==================== +*/ +void NET_OpenSocks( int port ) { + struct sockaddr_in address; + int err; +#ifndef _XBOX + struct hostent *h; +#endif + int len; + qboolean rfc1929; + unsigned char buf[64]; + + usingSocks = qfalse; + + Com_Printf( "Opening connection to SOCKS server.\n" ); + + if ( ( socks_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) { + err = WSAGetLastError(); + Com_Printf( "WARNING: NET_OpenSocks: socket: %s\n", NET_ErrorString() ); + return; + } + +#ifndef _XBOX + h = gethostbyname( net_socksServer->string ); + if ( h == NULL ) { + err = WSAGetLastError(); + Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: %s\n", NET_ErrorString() ); + return; + } + if ( h->h_addrtype != AF_INET ) { + Com_Printf( "WARNING: NET_OpenSocks: gethostbyname: address type was not AF_INET\n" ); + return; + } + address.sin_family = AF_INET; + address.sin_addr.s_addr = *(int *)h->h_addr_list[0]; + address.sin_port = htons( (short)net_socksPort->integer ); +#else + address.sin_family = AF_INET; + address.sin_addr.s_addr = inet_addr(net_socksServer->string); + address.sin_port = htons( (short)net_socksPort->integer ); +#endif //_XBOX + + if ( connect( socks_socket, (struct sockaddr *)&address, sizeof( address ) ) == SOCKET_ERROR ) { + err = WSAGetLastError(); + Com_Printf( "NET_OpenSocks: connect: %s\n", NET_ErrorString() ); + return; + } + + // send socks authentication handshake + if ( *net_socksUsername->string || *net_socksPassword->string ) { + rfc1929 = qtrue; + } + else { + rfc1929 = qfalse; + } + + buf[0] = 5; // SOCKS version + // method count + if ( rfc1929 ) { + buf[1] = 2; + len = 4; + } + else { + buf[1] = 1; + len = 3; + } + buf[2] = 0; // method #1 - method id #00: no authentication + if ( rfc1929 ) { + buf[2] = 2; // method #2 - method id #02: username/password + } + if ( send( socks_socket, (const char *)buf, len, 0 ) == SOCKET_ERROR ) { + err = WSAGetLastError(); + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + err = WSAGetLastError(); + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 5 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + switch( buf[1] ) { + case 0: // no authentication + break; + case 2: // username/password authentication + break; + default: + Com_Printf( "NET_OpenSocks: request denied\n" ); + return; + } + + // do username/password authentication if needed + if ( buf[1] == 2 ) { + int ulen; + int plen; + + // build the request + ulen = strlen( net_socksUsername->string ); + plen = strlen( net_socksPassword->string ); + + buf[0] = 1; // username/password authentication version + buf[1] = ulen; + if ( ulen ) { + memcpy( &buf[2], net_socksUsername->string, ulen ); + } + buf[2 + ulen] = plen; + if ( plen ) { + memcpy( &buf[3 + ulen], net_socksPassword->string, plen ); + } + + // send it + if ( send( socks_socket, (const char *)buf, 3 + ulen + plen, 0 ) == SOCKET_ERROR ) { + err = WSAGetLastError(); + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if ( len == SOCKET_ERROR ) { + err = WSAGetLastError(); + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if ( len != 2 || buf[0] != 1 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + if ( buf[1] != 0 ) { + Com_Printf( "NET_OpenSocks: authentication failed\n" ); + return; + } + } + + // send the UDP associate request + buf[0] = 5; // SOCKS version + buf[1] = 3; // command: UDP associate + buf[2] = 0; // reserved + buf[3] = 1; // address type: IPV4 + *(int *)&buf[4] = INADDR_ANY; + *(short *)&buf[8] = htons( (short)port ); // port + if ( send( socks_socket, (const char *)buf, 10, 0 ) == SOCKET_ERROR ) { + err = WSAGetLastError(); + Com_Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() ); + return; + } + + // get the response + len = recv( socks_socket, (char *)buf, 64, 0 ); + if( len == SOCKET_ERROR ) { + err = WSAGetLastError(); + Com_Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() ); + return; + } + if( len < 2 || buf[0] != 5 ) { + Com_Printf( "NET_OpenSocks: bad response\n" ); + return; + } + // check completion code + if( buf[1] != 0 ) { + Com_Printf( "NET_OpenSocks: request denied: %i\n", buf[1] ); + return; + } + if( buf[3] != 1 ) { + Com_Printf( "NET_OpenSocks: relay address is not IPV4: %i\n", buf[3] ); + return; + } + ((struct sockaddr_in *)&socksRelayAddr)->sin_family = AF_INET; + ((struct sockaddr_in *)&socksRelayAddr)->sin_addr.s_addr = *(int *)&buf[4]; + ((struct sockaddr_in *)&socksRelayAddr)->sin_port = *(short *)&buf[8]; + memset( ((struct sockaddr_in *)&socksRelayAddr)->sin_zero, 0, 8 ); + + usingSocks = qtrue; +} + + +/* +===================== +NET_GetLocalAddress +===================== +*/ +void NET_GetLocalAddress( void ) +{ +#ifndef _XBOX + + char hostname[256]; + struct hostent *hostInfo; + int error; + char *p; + int ip; + int n; + + // Set this early so we can just return if there is an error + numIP = 0; + + if( gethostname( hostname, 256 ) == SOCKET_ERROR ) { + error = WSAGetLastError(); + return; + } + + hostInfo = gethostbyname( hostname ); + if( !hostInfo ) { + error = WSAGetLastError(); + 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; + } + + 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 ); + numIP++; + } + +#else + XNADDR xnMyAddr; + DWORD dwStatus; + do + { + // Repeat while pending; OK to do other work in this loop + dwStatus = XNetGetTitleXnAddr( &xnMyAddr ); + } while( dwStatus == XNET_GET_XNADDR_PENDING ); + + // Error checking + if( dwStatus == XNET_GET_XNADDR_NONE ) + { + assert(!"Error getting XBox title address."); + return; + } + + *(u_long*)&localIP[0] = xnMyAddr.ina.S_un.S_addr; + *(u_long*)localIP[1] = 0; + *(u_long*)localIP[2] = 0; + *(u_long*)localIP[3] = 0; + + Com_Printf( "IP: %i.%i.%i.%i\n", localIP[0], localIP[1], localIP[2], localIP[3] ); + +#endif +} + +/* +==================== +NET_OpenIP +==================== +*/ +void NET_OpenIP( void ) +{ + cvar_t *ip; + int port; + int i; + + ip = Cvar_Get( "net_ip", "localhost", CVAR_LATCH ); + port = Cvar_Get( "net_port", va( "%i", PORT_SERVER ), CVAR_LATCH )->integer; + + // automatically scan for a valid port, so multiple + // dedicated servers can be started without requiring + // a different net_port for each one + for( i = 0 ; i < 10 ; i++ ) { + ip_socket = NET_IPSocket( ip->string, port + i ); + if ( ip_socket ) { + Cvar_SetValue( "net_port", port + i ); + if ( net_socksEnabled->integer ) { + NET_OpenSocks( port + i ); + } + NET_GetLocalAddress(); + return; + } + } + Com_Printf( "WARNING: Couldn't allocate IP port\n"); +} + + +/* +==================== +NET_IPXSocket +==================== +*/ +int NET_IPXSocket( int port ) +{ +#ifdef _XBOX + assert(!"NET_IPXSocket() - Not supported"); + return INVALID_SOCKET; +#else + SOCKET newsocket; + + struct sockaddr_ipx address; + int _true = 1; + int err; + + if( ( newsocket = socket( AF_IPX, SOCK_DGRAM, NSPROTO_IPX ) ) == INVALID_SOCKET ) { + err = WSAGetLastError(); + if (err != WSAEAFNOSUPPORT) { + Com_Printf( "WARNING: IPX_Socket: socket: %s\n", NET_ErrorString() ); + } + return INVALID_SOCKET; + } + + // make it non-blocking + if( ioctlsocket( newsocket, FIONBIO, (unsigned long *)&_true ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: IPX_Socket: ioctl FIONBIO: %s\n", NET_ErrorString() ); + return INVALID_SOCKET; + } + + // make it broadcast capable + if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&_true, sizeof( _true ) ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: IPX_Socket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString() ); + return INVALID_SOCKET; + } + + address.sa_family = AF_IPX; + memset( address.sa_netnum, 0, 4 ); + memset( address.sa_nodenum, 0, 6 ); + if( port == PORT_ANY ) { + address.sa_socket = 0; + } + else { + address.sa_socket = htons( (short)port ); + } + + if( bind( newsocket, (const struct sockaddr *)&address, sizeof(address) ) == SOCKET_ERROR ) { + Com_Printf( "WARNING: IPX_Socket: bind: %s\n", NET_ErrorString() ); + closesocket( newsocket ); + return INVALID_SOCKET; + } + return newsocket; +#endif +} + + +/* +==================== +NET_OpenIPX +==================== +*/ +void NET_OpenIPX( void ) { + int port; + + port = Cvar_Get( "net_port", va( "%i", PORT_SERVER ), CVAR_LATCH )->integer; + ipx_socket = NET_IPXSocket( port ); +} + + + +//=================================================================== + + +/* +==================== +NET_GetCvars +==================== +*/ +static qboolean NET_GetCvars( void ) { + qboolean modified; + + modified = qfalse; + + if( net_noudp && net_noudp->modified ) { + modified = qtrue; + } + net_noudp = Cvar_Get( "net_noudp", "0", CVAR_LATCH | CVAR_ARCHIVE ); + + if( net_noipx && net_noipx->modified ) { + modified = qtrue; + } + net_noipx = Cvar_Get( "net_noipx", "1", CVAR_LATCH | CVAR_ARCHIVE ); + + + if( net_forcenonlocal && net_forcenonlocal->modified ) { + modified = qtrue; + } + net_forcenonlocal = Cvar_Get( "net_forcenonlocal", "0", CVAR_LATCH | CVAR_ARCHIVE ); + + + if( net_socksEnabled && net_socksEnabled->modified ) { + modified = qtrue; + } + net_socksEnabled = Cvar_Get( "net_socksEnabled", "0", CVAR_LATCH | CVAR_ARCHIVE ); + + if( net_socksServer && net_socksServer->modified ) { + modified = qtrue; + } + net_socksServer = Cvar_Get( "net_socksServer", "", CVAR_LATCH | CVAR_ARCHIVE ); + + if( net_socksPort && net_socksPort->modified ) { + modified = qtrue; + } + net_socksPort = Cvar_Get( "net_socksPort", "1080", CVAR_LATCH | CVAR_ARCHIVE ); + + if( net_socksUsername && net_socksUsername->modified ) { + modified = qtrue; + } + net_socksUsername = Cvar_Get( "net_socksUsername", "", CVAR_LATCH | CVAR_ARCHIVE ); + + if( net_socksPassword && net_socksPassword->modified ) { + modified = qtrue; + } + net_socksPassword = Cvar_Get( "net_socksPassword", "", CVAR_LATCH | CVAR_ARCHIVE ); + + + return modified; +} + + +/* +==================== +NET_Config +==================== +*/ +void NET_Config( qboolean enableNetworking ) { + qboolean modified; + qboolean stop; + qboolean start; + + // get any latched changes to cvars + modified = NET_GetCvars(); + + if( net_noudp->integer && net_noipx->integer ) { + enableNetworking = qfalse; + } + + // if enable state is the same and no cvars were modified, we have nothing to do + if( enableNetworking == networkingEnabled && !modified ) { + return; + } + + if( enableNetworking == networkingEnabled ) { + if( enableNetworking ) { + stop = qtrue; + start = qtrue; + } + else { + stop = qfalse; + start = qfalse; + } + } + else { + if( enableNetworking ) { + stop = qfalse; + start = qtrue; + } + else { + stop = qtrue; + start = qfalse; + } + networkingEnabled = enableNetworking; + } + + if( stop ) { + if ( ip_socket && ip_socket != INVALID_SOCKET ) { + closesocket( ip_socket ); + ip_socket = INVALID_SOCKET; + } + + if ( socks_socket && socks_socket != INVALID_SOCKET ) { + closesocket( socks_socket ); + socks_socket = INVALID_SOCKET; + } + + if ( ipx_socket && ipx_socket != INVALID_SOCKET ) { + closesocket( ipx_socket ); + ipx_socket = INVALID_SOCKET; + } + } + + if( start ) { + if (! net_noudp->integer ) { + NET_OpenIP(); + } +#ifndef _XBOX + if (! net_noipx->integer ) { + NET_OpenIPX(); + } +#endif + } +} + + +/* +==================== +NET_Init +==================== +*/ +void NET_Init( void ) { + int r; + +#ifdef _XBOX + // Run NetStartup with security bypassed + // this allows us to communicate with PCs while developing + XNetStartupParams xnsp; + ZeroMemory( &xnsp, sizeof(xnsp) ); + xnsp.cfgSizeOfStruct = sizeof(xnsp); + +#ifdef _DEBUG + xnsp.cfgFlags |= XNET_STARTUP_BYPASS_SECURITY; +#else + xnsp.cfgFlags |= XNET_STARTUP_BYPASS_SECURITY; +// xnsp.cfgFlags = 0; +#endif + + INT err = XNetStartup( &xnsp ); +#endif + + r = WSAStartup( MAKEWORD( 1, 1 ), &winsockdata ); + if( r ) { + Com_Printf( "WARNING: Winsock initialization failed, returned %d\n", r ); + return; + } + + winsockInitialized = qtrue; + Com_Printf( "Winsock Initialized\n" ); + + // this is really just to get the cvars registered + NET_GetCvars(); + + //FIXME testing! + NET_Config( qtrue ); +} + + +/* +==================== +NET_Shutdown +==================== +*/ +void NET_Shutdown( void ) { + if ( !winsockInitialized ) { + return; + } + + NET_Config( qfalse ); + WSACleanup(); + winsockInitialized = qfalse; +} + + +/* +==================== +NET_Sleep + +sleeps msec or until net socket is ready +==================== +*/ +void NET_Sleep( int msec ) { +} + + +/* +==================== +NET_Restart_f +==================== +*/ +void NET_Restart( void ) { + NET_Config( networkingEnabled ); +} diff --git a/codemp/win32/win_qal_xbox.cpp b/codemp/win32/win_qal_xbox.cpp new file mode 100644 index 0000000..0992cda --- /dev/null +++ b/codemp/win32/win_qal_xbox.cpp @@ -0,0 +1,1321 @@ + +/* + * 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; + LPDSEFFECTIMAGEDESC m_ImageDesc; + + 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; + +/*********************************************** +* +* HACK - Voice initialization needs this +* +************************************************/ + +LPDSEFFECTIMAGEDESC getEffectsImageDesc(void) +{ + return s_pState->m_ImageDesc; +} + + +/*********************************************** +* +* DEVICES AND CONTEXTS +* +************************************************/ + +ALCdevice* alcOpenDevice(ALCubyte *deviceName) +{ + if (s_pState) return NULL; + s_pState = new QALState; + s_pState->m_Stream.m_Queue = new QALState::StreamInfo::queue_t; + + 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; + } + + DSEFFECTIMAGELOC effect; + effect.dwI3DL2ReverbIndex = GraphI3DL2_I3DL2Reverb; + effect.dwCrosstalkIndex = GraphXTalk_XTalk; + s_pState->m_SoundObject->DownloadEffectsImage(image, len, &effect, &s_pState->m_ImageDesc); + + 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 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 * 10.f, DS3D_DEFERRED); + v->second->SetMinDistance(value, DS3D_DEFERRED); +// v->second->SetMinDistance(value / 2.f, DS3D_DEFERRED); + } +} + +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; + } + + const char* name = Sys_GetFileCodeName(file); + + 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 + WaitForSingleObject(s_pState->m_Stream.m_Mutex, INFINITE); + s_pState->m_Stream.m_Queue->push_back(req); + ReleaseMutex(s_pState->m_Stream.m_Mutex); + + // 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); +} + +void Sys_StreamRequestQueueClear(void) +{ + WaitForSingleObject(s_pState->m_Stream.m_Mutex, INFINITE); + delete s_pState->m_Stream.m_Queue; + s_pState->m_Stream.m_Queue = new QALState::StreamInfo::queue_t; + ReleaseMutex(s_pState->m_Stream.m_Mutex); +} + +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, 0, + _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.f) + { + // mute the sound + voice->SetVolume(DSBVOLUME_MIN); + } + else + { + // convert to dB + g = 20.f * log10(g); + + if(g < -100.0f) { + g = -100.0f; + } + + // set the volume + voice->SetVolume(g * 100.f); + } +} + +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.f) + { + // mute the sound + s_pState->m_Stream.m_pVoice->SetVolume(DSBVOLUME_MIN); + } + else + { + // convert to dB + g = 20.f * log10(g); + + if(g < -100.0f) { + g = -100.0f; + } + + // set the volume + s_pState->m_Stream.m_pVoice->SetVolume(g * 100.f); + } + + 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/codemp/win32/win_qgl.cpp b/codemp/win32/win_qgl.cpp new file mode 100644 index 0000000..3c537e7 --- /dev/null +++ b/codemp/win32/win_qgl.cpp @@ -0,0 +1,4271 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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 m, GLfloat *values); //rwwRMG - map->m (avoid map type conflict) +void ( APIENTRY * qglGetPixelMapuiv )(GLenum m, GLuint *values); //rwwRMG - map->m (avoid map type conflict) +void ( APIENTRY * qglGetPixelMapusv )(GLenum m, GLushort *values); //rwwRMG - map->m (avoid map type conflict) +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 m, GLsizei mapsize, const GLfloat *values); //rwwRMG - map->m (avoid map type conflict) +void ( APIENTRY * qglPixelMapuiv )(GLenum m, GLsizei mapsize, const GLuint *values); //rwwRMG - map->m (avoid map type conflict) +void ( APIENTRY * qglPixelMapusv )(GLenum m, GLsizei mapsize, const GLushort *values); //rwwRMG - map->m (avoid map type conflict) +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 ) +{ +// Com_Printf ("...shutting down QGL\n" ); + + if ( glw_state.hinstOpenGL ) + { +// Com_Printf ("...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 ); + + Com_Printf ("...initializing QGL\n" ); + +// Com_Printf ("...calling LoadLibrary( '%s.dll' ): ", dllname ); + + if ( ( glw_state.hinstOpenGL = LoadLibraryEx( dllname, 0, 0 ) ) == 0 ) + { + DWORD dw = GetLastError(); + Com_Printf ("failed %u\n", dw ); + return qfalse; + } + Com_Printf ("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 = ( GLuint (__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 = ( GLenum (__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" ); + 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; + qglTexImage3DEXT = NULL; + qglTexSubImage3DEXT = NULL; + + // check logging + QGL_EnableLogging( (qboolean)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 ); + + 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/codemp/win32/win_qgl_dx8.cpp b/codemp/win32/win_qgl_dx8.cpp new file mode 100644 index 0000000..2c9a72f --- /dev/null +++ b/codemp/win32/win_qgl_dx8.cpp @@ -0,0 +1,6495 @@ + +/* + * 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" + +#ifdef _XBOX +#include +#include "win_lighteffects.h" +#include "win_highdynamicrange.h" + +#ifndef FINAL_BUILD +#include +#endif + +#endif + +#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 0 + +int texMemSize = 0; + +#if MEMORY_PROFILE + +static int getTexMemSize(IDirect3DTexture8* 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 ( * 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 + + + +#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) y = 0; + else if (y > glConfig.vidHeight) y = glConfig.vidHeight; + + if (width < 0) width = 0; +// 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; + else 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); \ +} + +/* +================= +_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) +{ + 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->Release(); + 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, cnt; + 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) + { + 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 + _updateDrawStride(normals, tex0, tex1); + _updateShader(normals, tex0, tex1); + _updateTextures(); + _updateMatrices(); + + glw_state->drawStride += normals ? 2 : 1; + + 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; + + // 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; + } + + // 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 + + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_XYZ, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 4;//3; + + if(normals) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_NORMAL, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 4;//3; + } + + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_COLOR, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes;//1; + + if(tex0) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_TEX0, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + stream += tess.numVertexes * 2;//2; + } + + if(tex1) + { + *glw_state->drawArray++ = D3DPUSH_ENCODE(CMD_VERTEXSTREAM_TEX1, 1); + *glw_state->drawArray++ = (DWORD)stream & 0x7fffffff; + } + + // 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 +} + +// EXTENSION: End drawing for a frame +static void dllEndFrame(void) +{ + assert(!glw_state->inDrawBlock); + + // the blend state can get reset by Present()... + GLboolean blend = qglIsEnabled(GL_BLEND); + + glw_state->device->EndScene(); + + qglViewport(0, 0, glConfig.vidWidth, glConfig.vidHeight); + 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_TEXTURE_SIZE: params[0] = (T)256; 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_HACK() +{ + _updateDrawStride(glw_state->normalArrayState, + glw_state->texCoordArrayState[0] ? 1 : 0, + glw_state->texCoordArrayState[1] ? 1 : 0); + _updateShader(glw_state->normalArrayState, + glw_state->texCoordArrayState[0] ? 1 : 0, + glw_state->texCoordArrayState[1] ? 1 : 0); + _updateTextures(); + _updateMatrices(); + + 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); + } +} + + +void renderObject_Light() +{ + //glw_state->drawStride = 11; + + //glw_state->primitiveMode = D3DPT_TRIANGLESTRIP; + // + //// get the necessary draw function + //drawelemfunc_t func = _drawElementsLightShader; + //int stride = glw_state->drawStride; + + ///*if(tess.currentPass == 0) + // buildStrips(glw_state->strip_lengths, &glw_state->num_strip_lengths, glw_state->strip_dest, + // &tess.numIndexes, &tess.indexes[0]);*/ + + //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); + //} + + // start the draw mode + assert(!glw_state->inDrawBlock); + + glw_state->inDrawBlock = true; + glw_state->primitiveMode = D3DPT_TRIANGLELIST; + + glw_state->drawStride = 11; + + // set vertex counters + glw_state->numVertices = 0; + glw_state->totalVertices = tess.numIndexes; + glw_state->maxVertices = _getMaxVerts(); + + // open a draw packet + //int num_packets = ((tess.numIndexes * glw_state->drawStride) / GLW_MAX_DRAW_PACKET_SIZE) + 1; + int num_packets; + if(tess.numIndexes == 0) { + num_packets = 1; + } else { + num_packets = (tess.numIndexes / glw_state->maxVertices) + (!!(tess.numIndexes % glw_state->maxVertices)); + } + int cmd_size = num_packets * 3; + int vert_size = glw_state->drawStride * tess.numIndexes; + + glw_state->device->BeginPush(vert_size + cmd_size + 2, + &glw_state->drawArray); + + glw_state->drawArray = _restartDrawPacket( + glw_state->drawArray, glw_state->maxVertices); + + // get the draw function we need + drawelemfunc_t func = _drawElementsLightShader; + + // loop taking care not to draw too much at a time + int inc = glw_state->maxVertices; + for (int start = 0; ; start += inc) + { + // draw glw_state->maxVertices amount of geometry + func(glw_state->maxVertices, &(((GLushort*)tess.indexes)[start])); + + // 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); + } + + // finish up the draw + qglEnd(); +} + +void renderObject_Bump() +{ + // start the draw mode + assert(!glw_state->inDrawBlock); + + glw_state->inDrawBlock = true; + glw_state->primitiveMode = D3DPT_TRIANGLELIST; + + glw_state->drawStride = 13; + + // set vertex counters + glw_state->numVertices = 0; + glw_state->totalVertices = tess.numIndexes; + glw_state->maxVertices = _getMaxVerts(); + + // open a draw packet + //int num_packets = ((tess.numIndexes * glw_state->drawStride) / GLW_MAX_DRAW_PACKET_SIZE) + 1; + int num_packets; + if(tess.numIndexes == 0) { + num_packets = 1; + } else { + num_packets = (tess.numIndexes / glw_state->maxVertices) + (!!(tess.numIndexes % glw_state->maxVertices)); + } + int cmd_size = num_packets * 3; + int vert_size = glw_state->drawStride * tess.numIndexes; + + glw_state->device->BeginPush(vert_size + cmd_size + 2, + &glw_state->drawArray); + + glw_state->drawArray = _restartDrawPacket( + glw_state->drawArray, glw_state->maxVertices); + + // get the draw function we need + drawelemfunc_t func = _drawElementsBumpShader; + + // loop taking care not to draw too much at a time + int inc = glw_state->maxVertices; + for (int start = 0; ; start += inc) + { + // draw glw_state->maxVertices amount of geometry + func(glw_state->maxVertices, &(((GLushort*)tess.indexes)[start])); + + // 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); + } + + // finish up the draw + qglEnd(); +} + +void renderObject_Env() +{ + //glw_state->drawStride = 7; + //_updateTextures(); + + //glw_state->primitiveMode = D3DPT_TRIANGLESTRIP; + // + //// get the necessary draw function + //drawelemfunc_t func = _drawElementsVCN; + //int stride = glw_state->drawStride; + + //if(tess.currentPass == 0) + //{ + // buildStrips(glw_state->strip_lengths, &glw_state->num_strip_lengths, glw_state->strip_dest, + // &tess.numIndexes, &tess.indexes[0]); + //} + + //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); + //} + + // start the draw mode + assert(!glw_state->inDrawBlock); + + glw_state->inDrawBlock = true; + glw_state->primitiveMode = D3DPT_TRIANGLELIST; + + glw_state->drawStride = 7; + _updateTextures(); + + // set vertex counters + glw_state->numVertices = 0; + glw_state->totalVertices = tess.numIndexes; + glw_state->maxVertices = _getMaxVerts(); + + // open a draw packet + //int num_packets = ((tess.numIndexes * glw_state->drawStride) / GLW_MAX_DRAW_PACKET_SIZE) + 1; + int num_packets; + if(tess.numIndexes == 0) { + num_packets = 1; + } else { + num_packets = (tess.numIndexes / glw_state->maxVertices) + (!!(tess.numIndexes % glw_state->maxVertices)); + } + int cmd_size = num_packets * 3; + int vert_size = glw_state->drawStride * tess.numIndexes; + + glw_state->device->BeginPush(vert_size + cmd_size + 2, + &glw_state->drawArray); + + glw_state->drawArray = _restartDrawPacket( + glw_state->drawArray, glw_state->maxVertices); + + // get the draw function we need + drawelemfunc_t func = _drawElementsVCN; + + // loop taking care not to draw too much at a time + int inc = glw_state->maxVertices; + for (int start = 0; ; start += inc) + { + // draw glw_state->maxVertices amount of geometry + func(glw_state->maxVertices, &(((GLushort*)tess.indexes)[start])); + + // 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); + } + + // finish up the draw + qglEnd(); +} +#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) +{ +#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 +} + +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) +{ + assert(format == GL_RGBA && type == GL_UNSIGNED_BYTE); + + /* + // create a temporary storage surface + IDirect3DSurface8 *target; + glw_state->device->CreateImageSurface(twidth, theight, D3DFMT_A8R8G8B8, &target); + + // get a pointer to the back buffer + IDirect3DSurface8* screen; + glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &screen); + + // copy the back buffer into a surface of the appropriate size and format + RECT r; + r.left = x; + r.top = y; + r.right = x + width; + r.bottom = y + height; + D3DXLoadSurfaceFromSurface(target, NULL, NULL, screen, NULL, &r, D3DX_DEFAULT, 0); + screen->Release(); + + // lock the target surface + D3DLOCKED_RECT lock; + target->LockRect(&lock, NULL, D3DLOCK_READONLY); + + // copy the pixel data + for (int y = 0; y < theight; ++y) + { + memcpy((char*)pixels + twidth * y * 4, + (char*)lock.pBits + lock.Pitch * y, + twidth * 4); + } + + // all done + target->UnlockRect(); + target->Release(); + */ + + /* + // create target storage surface + IDirect3DSurface8 *target; + glw_state->device->CreateImageSurface(twidth, theight, D3DFMT_A8R8G8B8, &target); + + // get a pointer to the back buffer + IDirect3DSurface8* back; + glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &back); + + D3DSURFACE_DESC ddsd; + back->GetDesc(&ddsd); + int bpp = XGBytesPerPixelFromFormat(ddsd.Format); + + // create surface to hold screen data + // (512x512 is exactly big enough to hold the screen but we + // need to save some memory so I'm going to clip the edges) + IDirect3DSurface8 *screen; + glw_state->device->CreateImageSurface(512, 512, ddsd.Format, &screen); + + // copy back buffer to non-tiled screen surface + RECT r; + r.left = 64; + r.top = 0; + r.right = 576; + r.bottom = 480; + POINT ul = {0, 0}; + glw_state->device->CopyRects(back, &r, 1, screen, &ul); + back->Release(); + + // deswizzle the screen + D3DLOCKED_RECT lock; + screen->LockRect(&lock, NULL, D3DLOCK_READONLY); + void* deswizzled = Z_Malloc(512*512*bpp, TAG_TEMP_WORKSPACE, qfalse, 16); + XGUnswizzleRect(lock.pBits, 512, 512, NULL, deswizzled, 0, NULL, bpp); + screen->UnlockRect(); + screen->Release(); + + // copy the screen into a surface of the appropriate size and format + r.left = 0; + r.top = 0; + r.right = 512; + r.bottom = 480; + D3DXLoadSurfaceFromMemory(target, NULL, NULL, deswizzled, ddsd.Format, + 512*bpp, NULL, &r, D3DX_DEFAULT, 0); + Z_Free(deswizzled); + + // lock the target surface + target->LockRect(&lock, NULL, D3DLOCK_READONLY); + + // copy the pixel data + for (int y = 0; y < theight; ++y) + { + memcpy((char*)pixels + twidth * y * 4, + (char*)lock.pBits + lock.Pitch * y, + twidth * 4); + } + + // all done + target->UnlockRect(); + target->Release(); + */ +} + +/********** +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 +**********/ +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; + D3DBaseTexture* pTexStage0; + D3DBaseTexture* pTexStage1; + D3DBaseTexture* pTexStage2; + D3DBaseTexture* pTexStage3; + 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 ); + glw_state->device->GetTexture(0,&pTexStage0); + glw_state->device->GetTexture(1,&pTexStage1); + glw_state->device->GetTexture(2,&pTexStage2); + glw_state->device->GetTexture(3,&pTexStage3); + 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) + { + int refCount; + refCount = info->mipmap->Release(); + + // Had to remove this. Multiple characters using force push in one + // frame triggers the assert. Things clean up by the end of the frame. +// assert(refCount == 0); + + glw_state->device->CreateTexture( width, + height, + 1, + 0, + desc.Format, + 0, + &info->mipmap ); + } + + // 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 + glw_state->device->CreateTexture( w, + h, + 1, + 0, + D3DFMT_LIN_X8R8G8B8, + 0, + &pRenderTex ); + } + 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); + + // clear the other texture stages + glw_state->device->SetTexture(1, NULL); + glw_state->device->SetTexture(2, NULL); + glw_state->device->SetTexture(3, NULL); + + // 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); + + glw_state->device->SetTexture(0,pTexStage0); + glw_state->device->SetTexture(1,pTexStage1); + glw_state->device->SetTexture(2,pTexStage2); + glw_state->device->SetTexture(3,pTexStage3); + 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 ); + + // release our surfaces/textures + if(pTexStage0) + pTexStage0->Release(); + + if(pTexStage1) + pTexStage1->Release(); + + if(pTexStage2) + pTexStage2->Release(); + + if(pTexStage3) + pTexStage3->Release(); + + 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 _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)); + } +} + +static void _texImageDDS(glwstate_t::TextureInfo* info, GLint numlevels, GLsizei width, GLsizei height, GLenum format, const GLvoid *pixels) +{ + _d3d_check(D3DXCreateTextureFromFileInMemoryEx(glw_state->device, + pixels, + Z_Size(const_cast(pixels)), + width, + height, + numlevels, + 0, + D3DFMT_UNKNOWN, + D3DPOOL_MANAGED, + D3DX_DEFAULT, + D3DX_DEFAULT, + 0, + NULL, + NULL, + &info->mipmap), + "D3DXCreateTextureFromFileInMemoryEx"); +} + +static void _texImageRGBA(glwstate_t::TextureInfo* info, GLint numlevels, GLint internalformat, GLsizei width, GLsizei height, GLenum format, const GLvoid *pixels) +{ + IDirect3DSurface8 *pSurf = NULL; + D3DFORMAT f; + int bpp; + RECT srcRect; + + srcRect.top = 0; + srcRect.left = 0; + srcRect.right = width; + srcRect.bottom = height; + + switch(format) + { + case GL_RGB: + f = D3DFMT_X8R8G8B8; + bpp = 3; + break; + + case GL_RGBA: + f = D3DFMT_A8R8G8B8; + bpp = 4; + break; + + case GL_RGB_SWIZZLE_EXT: + f = D3DFMT_R5G6B5; + bpp = 2; + break; + + case GL_LIN_RGB8: + f = D3DFMT_X8R8G8B8; + bpp = 4; + break; + + default: + assert(0); + } + + _d3d_check(glw_state->device->CreateImageSurface(width, height, f, &pSurf), + "CreateImageSurface"); + + _d3d_check(D3DXLoadSurfaceFromMemory(pSurf, + NULL, + NULL, + (LPCVOID)pixels, + f, + width * bpp, + NULL, + &srcRect, + D3DX_FILTER_NONE, + 0), + "D3DXLoadSurfaceFromMemory"); + + switch(internalformat) + { + case GL_RGB5: + case GL_RGB4_S3TC: + f = D3DFMT_R5G6B5; + break; + + case GL_RGBA4: + f = D3DFMT_A4R4G4B4; + break; + + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + f = D3DFMT_DXT1; + break; + + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + f = D3DFMT_DXT5; + break; + + case GL_RGB8: + case 3: + f = D3DFMT_X8R8G8B8; + break; + + case GL_RGBA8: + case 4: + f = D3DFMT_A8R8G8B8; + break; + + case GL_LIN_RGB8: + f = D3DFMT_LIN_X8R8G8B8; + break; + + default: + assert(0); + } + + _d3d_check(D3DXCreateTexture(glw_state->device, + width, + height, + numlevels, + 0, + f, + D3DPOOL_MANAGED, + &info->mipmap), + "D3DXCreateTexture"); + + LPDIRECT3DSURFACE8 txtSurf; + _d3d_check(info->mipmap->GetSurfaceLevel(0, &txtSurf), + "GetSurfaceLevel"); + + _d3d_check(D3DXLoadSurfaceFromSurface(txtSurf, NULL, NULL, + pSurf, NULL, NULL, D3DX_DEFAULT, 0), + "D3DXLoadSurfaceFromSurface"); + + txtSurf->Release(); + pSurf->Release(); + + if(numlevels > 1) + { + _d3d_check(D3DXFilterTexture(info->mipmap, + NULL, + D3DX_DEFAULT, + D3DX_DEFAULT), + "D3DXFilterTexture"); + } +} + +// 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; + info->mipmap->Release(); + } + // 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); + + // Only 16-bit textures supported for now! + D3DXLoadSurfaceFromMemory(surf, NULL, &dr, pixels, + D3DFMT_R5G6B5, width * 2, 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 ) +{ + Com_Printf ("...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; + qglCopyBackBufferToTexEXT = NULL; + qglCopyBackBufferToTex = 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 +}; + +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 = 720; + + if( XGetVideoFlags() & XC_VIDEO_FLAGS_HDTV_480p ) + { + width = 720; + 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 CG_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; + CG_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); + + 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->lightEffects = new LightEffects; +#endif // VV_LIGHTING + HDREffect.Initialize(); +#endif + +#ifndef FINAL_BUILD + Cmd_AddCommand("d3d_autoperf", D3D_AutoPerfData_f); +#endif +} + +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; +} + + +#if defined (_TAKESCREENS) +void QGL_SaveScreenshot(void) +{ + LPDIRECT3DSURFACE8 screenShot = 0; + glw_state->device->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &screenShot); + + // Copy over the screen shot to the new image that is 64x64 + LPDIRECT3DSURFACE8 compressedSaveGameImage = 0; + glw_state->device->CreateImageSurface( 64, 64, D3DFMT_DXT1, &compressedSaveGameImage ); + D3DXLoadSurfaceFromSurface( compressedSaveGameImage, NULL, NULL, screenShot, NULL, NULL, D3DX_DEFAULT, D3DCOLOR( 0 ) ); + + // Free the big screenshot 640x480x4? + if ( screenShot ) + screenShot->Release(); + + // Write out the saveimage to the utility drive + XGWriteSurfaceOrTextureToXPR( compressedSaveGameImage, "u:\\saveimage.xbx", TRUE ); + + // Free the compressed 64x64 image + if ( compressedSaveGameImage ) + compressedSaveGameImage->Release(); +} +#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/codemp/win32/win_shared.cpp b/codemp/win32/win_shared.cpp new file mode 100644 index 0000000..b32d1cc --- /dev/null +++ b/codemp/win32/win_shared.cpp @@ -0,0 +1,547 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "win_local.h" +#ifndef _XBOX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +/* +================ +Sys_Milliseconds +================ +*/ +int Sys_Milliseconds (bool baseTime) +{ + static int sys_timeBase = timeGetTime(); + int sys_curtime; + + sys_curtime = timeGetTime(); + if(!baseTime) + { + sys_curtime -= sys_timeBase; + } + + return sys_curtime; +} + +/* +================ +Sys_SnapVector +================ +*/ +void Sys_SnapVector( float *v ) +{ + int i; + float f; + + f = *v; + __asm fld f; + __asm fistp i; + *v = i; + v++; + f = *v; + __asm fld f; + __asm fistp i; + *v = i; + v++; + f = *v; + __asm fld f; + __asm fistp i; + *v = i; + /* + *v = myftol(*v); + v++; + *v = myftol(*v); + v++; + *v = myftol(*v); + */ +} + + +/* +** +** Disable all optimizations temporarily so this code works correctly! +** +*/ +#pragma optimize( "", off ) + +/* +** -------------------------------------------------------------------------------- +** +** 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 ) ) + 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 +} + +/* +** +** Re-enable optimizations back to what they were +** +*/ +#pragma optimize( "", on ) + +//============================================ + +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 +} + +char *Sys_DefaultHomePath(void) { + return NULL; +} + +char *Sys_DefaultInstallPath(void) +{ + return Sys_Cwd(); +} + +int Sys_GetPhysicalMemory( void ) +{ + MEMORYSTATUS MemoryStatus; + + memset( &MemoryStatus, sizeof(MEMORYSTATUS), 0 ); + MemoryStatus.dwLength = sizeof(MEMORYSTATUS); + + GlobalMemoryStatus( &MemoryStatus ); + + return( (int)(MemoryStatus.dwTotalPhys / (1024 * 1024)) + 1 ); + +} + + +#ifndef _XBOX +int Sys_GetCPUSpeedOld() +{ + timeBeginPeriod(1); + +#ifdef WIN32 + int iPriority; + HANDLE hThread = GetCurrentThread(); + + iPriority = GetThreadPriority(hThread); + if ( iPriority != THREAD_PRIORITY_ERROR_RETURN ) + { + SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL); + } +#endif // WIN32 + + DWORD clockStart = timeGetTime(); + DWORD clockEnd = clockStart + 100; + + unsigned long start; + unsigned long end; + + __asm + { + rdtsc + mov start, eax + } + + while(timeGetTime() < clockEnd) + { // loop for 1 tenth of a second + } + __asm + { + rdtsc + mov end, eax + } + + +#ifdef WIN32 + // Reset priority + if ( iPriority != THREAD_PRIORITY_ERROR_RETURN ) + { + SetThreadPriority(hThread, iPriority); + } +#endif // WIN32 + + timeEndPeriod(1); + + unsigned long time; + time = end - start; + int coarse = time / 100000; + int firsttry = floor((coarse + 25) / 50) * 50; + if (abs(firsttry - coarse) < 10) + { + return firsttry; + } + else + { + return floor(floor((coarse + 17) / 33.3) * 33.3); + } +} + +int Sys_GetCPUSpeed() +{ + unsigned long raw_freq; // Raw frequency of CPU in MHz + unsigned long norm_freq; // Normalized frequency of CPU in MHz. + LARGE_INTEGER t0,t1; // Variables for High-Resolution Performance Counter reads + + unsigned long freq =0; // Most current frequ. calculation + unsigned long freq2 =0; // 2nd most current frequ. calc. + unsigned long freq3 =0; // 3rd most current frequ. calc. + + unsigned long total; // Sum of previous three frequency calculations + int tries=0; // Number of times a calculation has been made on this call to cpuspeed + unsigned long total_cycles=0, cycles; // Clock cycles elapsed during test + unsigned long stamp0=0, stamp1=0; // Time Stamp Variable for beginning and end of test + unsigned long total_ticks=0, ticks; // Microseconds elapsed during test + LARGE_INTEGER count_freq; // High Resolution Performance Counter frequency + +#define TOLERANCE 1 // Number of MHz to allow samplings to deviate from average of samplings. +#define ROUND_THRESHOLD 6 + +#ifdef WIN32 + int iPriority; + HANDLE hThread = GetCurrentThread(); +#endif // WIN32; + + if ( !QueryPerformanceFrequency ( &count_freq ) ) + return Sys_GetCPUSpeedOld(); //should never happen + + // On processors supporting the Read + // Time Stamp opcode, compare elapsed + // time on the High-Resolution Counter + // with elapsed cycles on the Time + // Stamp Register. + + do { // This do loop runs up to 20 times or until the average of the previous + // three calculated frequencies is within 1 MHz of each of the + // individual calculated frequencies. This resampling increases the + // accuracy of the results since outside factors could affect this calculation + + tries++; // Increment number of times sampled on this call to cpuspeed + + freq3 = freq2; // Shift frequencies back to make + freq2 = freq; // room for new frequency measurement + + QueryPerformanceCounter(&t0);// Get high-resolution performance counter time + + t1 = t0; // Set Initial time + +#ifdef WIN32 + iPriority = GetThreadPriority(hThread); + if ( iPriority != THREAD_PRIORITY_ERROR_RETURN ) + { + SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL); + } +#endif // WIN32 + + while ( (unsigned long)t1.LowPart - (unsigned long)t0.LowPart<50) { + // Loop until 50 ticks have passed since last read of hi-res counter. This accounts for overhead later. + QueryPerformanceCounter(&t1); + _asm { + rdtsc; // Read Time Stamp + MOV stamp0, EAX + } + } + + t0 = t1; // Reset Initial Time + + while ((unsigned long)t1.LowPart-(unsigned long)t0.LowPart<2000 ) { + // Loop until enough ticks have passed since last read of hi-res counter. This allows for elapsed time for sampling. + QueryPerformanceCounter(&t1); + __asm { + rdtsc; // Read Time Stamp + MOV stamp1, EAX + } + } + +#ifdef WIN32 + if ( iPriority != THREAD_PRIORITY_ERROR_RETURN ) + { // Reset priority + SetThreadPriority(hThread, iPriority); + } +#endif // WIN32 + + cycles = stamp1 - stamp0; // Number of internal clock cycles is difference between two time stamp readings. + + ticks = (unsigned long) t1.LowPart - (unsigned long) t0.LowPart; + // Number of external ticks is difference between two hi-res counter reads. + + + // Note that some seemingly arbitrary mulitplies and + // divides are done below. This is to maintain a + // high level of precision without truncating the + // most significant data. According to what value + // ITERATIIONS is set to, these multiplies and + // divides might need to be shifted for optimal + // precision. + + ticks = ticks * 100000; // Convert ticks to hundred thousandths of a tick + + ticks = ticks / ( count_freq.LowPart/10 ); + // Hundred Thousandths of a Ticks / ( 10 ticks/second ) = microseconds (us) + + total_ticks += ticks; + total_cycles += cycles; + + if ( ticks%count_freq.LowPart > count_freq.LowPart/2 ) + ticks++; // Round up if necessary + + if (!ticks){ + ticks++; // prevent DIV by ZERO + } + + freq = cycles/ticks; // Cycles / us = MHz + + if ( cycles%ticks > ticks/2 ) + freq++; // Round up if necessary + + total = ( freq + freq2 + freq3 ); // Total last three frequency calculations + + } while ( (tries < 3 ) || + (tries < 20)&& + ((abs(3 * freq -total) > 3*TOLERANCE )|| + (abs(3 * freq2-total) > 3*TOLERANCE )|| + (abs(3 * freq3-total) > 3*TOLERANCE ))); + // Compare last three calculations to average of last three calculations. + + if (!total_ticks){ + total_ticks++; // prevent DIV by ZERO + } + + // Try one more significant digit. + freq3 = ( total_cycles * 10 ) / total_ticks; + freq2 = ( total_cycles * 100 ) / total_ticks; + + + if ( freq2 - (freq3 * 10) >= ROUND_THRESHOLD ) + freq3++; + + raw_freq = total_cycles / total_ticks; + norm_freq = raw_freq; + + freq = raw_freq * 10; + if( (freq3 - freq) >= ROUND_THRESHOLD ) + norm_freq++; + + return norm_freq; +} +#endif + diff --git a/codemp/win32/win_snd.cpp b/codemp/win32/win_snd.cpp new file mode 100644 index 0000000..ca45b5e --- /dev/null +++ b/codemp/win32/win_snd.cpp @@ -0,0 +1,382 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/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 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; +} + + +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: " ); + if (DS_OK != pDS->CreateSoundBuffer(&dsbuf, &pDSBuf, NULL)) { + Com_Printf( " - using ancient version of DirectX -- this will slow FPS\n" ); + dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY; + 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 (); + } +} + + diff --git a/codemp/win32/win_stream_dx8.cpp b/codemp/win32/win_stream_dx8.cpp new file mode 100644 index 0000000..6592fb0 --- /dev/null +++ b/codemp/win32/win_stream_dx8.cpp @@ -0,0 +1,290 @@ + +/* + * 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" + +#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 const char* Sys_GetFileCodeName(int code); +extern int Sys_GetFileCodeSize(int code); + +#define STREAM_MAX_OPEN 48 +struct StreamInfo +{ + volatile HANDLE 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; + + +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); + + // Process request + switch (req.type) + { + case IOREQ_OPEN: + strm = &s_Streams[req.handle]; + assert(strm->used); + + { + 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); + +# if STREAM_SLOW_READ + Sleep(200); +# endif + + 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) +{ + // 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, 0, _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 + int size = Sys_GetFileCodeSize(code); + 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; + } +} diff --git a/codemp/win32/win_syscon.cpp b/codemp/win32/win_syscon.cpp new file mode 100644 index 0000000..2ba3639 --- /dev/null +++ b/codemp/win32/win_syscon.cpp @@ -0,0 +1,574 @@ +// win_syscon.h +// 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/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) +{ + const char *cmdString; + static qboolean s_timePolarity; + + switch (uMsg) + { + case WM_ACTIVATE: + if ( LOWORD( wParam ) != WA_INACTIVE ) + { + SetFocus( s_wcd.hwndInputLine ); + } + + if ( com_viewlog && ( com_dedicated && !com_dedicated->integer ) ) + { + // 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: + if ( ( com_dedicated && com_dedicated->integer ) ) + { + cmdString = CopyString( "quit" ); + Sys_QueEvent( 0, SE_CONSOLE, 0, 0, strlen( cmdString ) + 1, (void *)cmdString ); + } + else 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, (void *)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 = (qboolean)!s_timePolarity; + if ( s_wcd.hwndErrorBox ) + { + InvalidateRect( s_wcd.hwndErrorBox, NULL, FALSE ); + } + } + break; + } + + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} + +extern void CompleteCommand( void ) ; + +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 ) ); + + strcpy(kg.g_consoleField.buffer, inputBuffer); + kg.historyEditLines[kg.nextHistoryLine % COMMAND_HISTORY] = kg.g_consoleField; + kg.nextHistoryLine++; + kg.historyLine = kg.nextHistoryLine; + + return 0; + } + else if (wParam == 9 ) + { + GetWindowText( s_wcd.hwndInputLine, inputBuffer, sizeof( inputBuffer ) ); + strcpy(kg.g_consoleField.buffer, inputBuffer); + CompleteCommand(); + SetWindowText( s_wcd.hwndInputLine, kg.g_consoleField.buffer); + SendMessage(s_wcd.hwndInputLine, EM_SETSEL, strlen(kg.g_consoleField.buffer) , MAKELONG(0xffff, 0xffff) ); + } + + case WM_KEYDOWN: + if (wParam == VK_UP) + { + if ( kg.nextHistoryLine - kg.historyLine < COMMAND_HISTORY && kg.historyLine > 0 ) + { + kg.historyLine--; + } + kg.g_consoleField = kg.historyEditLines[ kg.historyLine % COMMAND_HISTORY ]; + SetWindowText( s_wcd.hwndInputLine, kg.g_consoleField.buffer); + SendMessage(s_wcd.hwndInputLine, EM_SETSEL, strlen(kg.g_consoleField.buffer) , MAKELONG(0xffff, 0xffff) ); + return 0; + } + else if (wParam == VK_DOWN) + { + if (kg.historyLine == kg.nextHistoryLine) + { + return 0; + } + kg.historyLine++; + kg.g_consoleField = kg.historyEditLines[ kg.historyLine % COMMAND_HISTORY ]; + SetWindowText( s_wcd.hwndInputLine, kg.g_consoleField.buffer); + SendMessage(s_wcd.hwndInputLine, EM_SETSEL, strlen(kg.g_consoleField.buffer) , MAKELONG(0xffff, 0xffff) ); + return 0; + } + break; + } + + return CallWindowProc( s_wcd.SysInputLineWndProc, hWnd, uMsg, wParam, lParam ); +} + +/* +** Sys_CreateConsole +*/ +void Sys_CreateConsole( void ) +{ + HDC hDC; + WNDCLASS wc; + RECT rect; + const char *DEDCLASS = "JAMP 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; + 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 Academy MP 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 | WS_TABSTOP, + 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 | WS_TABSTOP, + 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 | WS_TABSTOP, + 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 | WS_TABSTOP, + 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 | WS_TABSTOP, + 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 ) + { + 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 ); + 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/codemp/win32/win_wndproc.cpp b/codemp/win32/win_wndproc.cpp new file mode 100644 index 0000000..c739be1 --- /dev/null +++ b/codemp/win32/win_wndproc.cpp @@ -0,0 +1,547 @@ +//Anything above this #include will be ignored by the compiler +#include "../qcommon/exe_headers.h" + +#include "../client/client.h" +#include "win_local.h" + +WinVars_t g_wv; + +// The only directly referenced keycode - the console key (which gives different ascii codes depending on locale) +#define CONSOLE_SCAN_CODE 0x29 + +#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 +static cvar_t *r_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 = (qboolean)minimize; + + Com_DPrintf("VID_AppActivate: %i\n", fActive ); + + 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) + { + // yeuch, but oh well... + // + if ( wParam >= VK_NUMPAD0 && wParam <= VK_NUMPAD9 ) + { + bool bNumlockOn = !!(GetKeyState( VK_NUMLOCK ) & 1); + if ( bNumlockOn ) + { + wParam = 0x30 + (wParam - VK_NUMPAD0); // convert to standard 0..9 + } + } + 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); + r_fullscreen = Cvar_Get ("r_fullscreen", "1", CVAR_ARCHIVE | CVAR_LATCH ); + + MSH_MOUSEWHEEL = RegisterWindowMessage("MSWHEEL_ROLLMSG"); + if ( r_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 ( r_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( (qboolean)(fActive != WA_INACTIVE && !fMinimized) ); + } + break; + + case WM_MOVE: + { + int xPos, yPos; + RECT r; + int style; + + if (!r_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 ) + { + if ( r_fullscreen && cl_allowAltEnter && + (cls.state==CA_DISCONNECTED || cls.state==CA_CONNECTED) + ) + { + if (cl_allowAltEnter->integer) + { + Cvar_SetValue( "r_fullscreen", !r_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/codemp/win32/winquake.rc b/codemp/win32/winquake.rc new file mode 100644 index 0000000..87ebafb --- /dev/null +++ b/codemp/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 "qe3.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 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "Raven Software" + VALUE "CompanyName", "Activision Inc" + VALUE "FileDescription", "Jedi Academy MultiPlayer" + VALUE "FileVersion", VERSION_STRING + VALUE "InternalName", "JA MP" + VALUE "LegalCopyright", "Copyright (C) 2003" + VALUE "OriginalFilename", "jamp.exe" + VALUE "ProductName", "Jedi Knight®: Jedi Academy (MP)" + VALUE "ProductVersion", VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + diff --git a/codemp/x_botlib/mssccprj.scc b/codemp/x_botlib/mssccprj.scc new file mode 100644 index 0000000..e91b3f3 --- /dev/null +++ b/codemp/x_botlib/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[x_botlib.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp/x_botlib", SUHAAAAA diff --git a/codemp/x_botlib/vssver.scc b/codemp/x_botlib/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..e36998b12c8bb3a0f7e55af8d8678e95172c6684 GIT binary patch literal 48 ycmXpJVq_>gDwNv&Y=_H|yDBkqiQiY4ecdKLO@sjqRsv~tj=q}R9lWVbKoJ0;N)FWk literal 0 HcmV?d00001 diff --git a/codemp/x_botlib/x_botlib.vcproj b/codemp/x_botlib/x_botlib.vcproj new file mode 100644 index 0000000..89a7062 --- /dev/null +++ b/codemp/x_botlib/x_botlib.vcproj @@ -0,0 +1,470 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/x_exe/mssccprj.scc b/codemp/x_exe/mssccprj.scc new file mode 100644 index 0000000..66fedab --- /dev/null +++ b/codemp/x_exe/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[x_exe.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp/x_exe", TUHAAAAA diff --git a/codemp/x_exe/vssver.scc b/codemp/x_exe/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..4afeb9c3fca2a7e9a630016c01d58f8365d81a9e GIT binary patch literal 48 ycmXpJVq_>gDwNv&Y=_H|yDBkqiQiWoDDJSCF2Vo?D}c25`@5NY4NOwGfFb~`Zx7%A literal 0 HcmV?d00001 diff --git a/codemp/x_exe/x_exe.vcproj b/codemp/x_exe/x_exe.vcproj new file mode 100644 index 0000000..6f36125 --- /dev/null +++ b/codemp/x_exe/x_exe.vcproj @@ -0,0 +1,1144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/x_jk2cgame/mssccprj.scc b/codemp/x_jk2cgame/mssccprj.scc new file mode 100644 index 0000000..3bcec23 --- /dev/null +++ b/codemp/x_jk2cgame/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[x_jk2cgame.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp/x_jk2cgame", UUHAAAAA diff --git a/codemp/x_jk2cgame/vssver.scc b/codemp/x_jk2cgame/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..ccaae4811f7c4cc280e7766f0be2666e00fc13ac GIT binary patch literal 48 ycmXpJVq_>gDwNv&Y=_H|yDBkqiQiXTidp%0h6n=~tOC-^#TFcM8&{<;14RI}pbx14 literal 0 HcmV?d00001 diff --git a/codemp/x_jk2cgame/x_jk2cgame.vcproj b/codemp/x_jk2cgame/x_jk2cgame.vcproj new file mode 100644 index 0000000..3a502f7 --- /dev/null +++ b/codemp/x_jk2cgame/x_jk2cgame.vcproj @@ -0,0 +1,977 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/x_jk2game/mssccprj.scc b/codemp/x_jk2game/mssccprj.scc new file mode 100644 index 0000000..eb9c743 --- /dev/null +++ b/codemp/x_jk2game/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[x_jk2game.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp/x_jk2game", VUHAAAAA diff --git a/codemp/x_jk2game/vssver.scc b/codemp/x_jk2game/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..dc6db291929be2890384b678a6e411bb7d7c2e25 GIT binary patch literal 48 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiW+iZ8o4Q-lEwRs-qxt5zI~KH`_c3KRhVsq+wK literal 0 HcmV?d00001 diff --git a/codemp/x_jk2game/x_jk2game.vcproj b/codemp/x_jk2game/x_jk2game.vcproj new file mode 100644 index 0000000..7d67072 --- /dev/null +++ b/codemp/x_jk2game/x_jk2game.vcproj @@ -0,0 +1,1648 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/x_ui/mssccprj.scc b/codemp/x_ui/mssccprj.scc new file mode 100644 index 0000000..8757bf4 --- /dev/null +++ b/codemp/x_ui/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[x_ui.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/codemp/x_ui", WUHAAAAA diff --git a/codemp/x_ui/vssver.scc b/codemp/x_ui/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..2864a4032a698039cc916327c54b8cc327f6f35d GIT binary patch literal 48 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZFYd>h8CBgs(Yk>6Kxf~yk{;Ek~28sXxvPux? literal 0 HcmV?d00001 diff --git a/codemp/x_ui/x_ui.vcproj b/codemp/x_ui/x_ui.vcproj new file mode 100644 index 0000000..bcdf2e9 --- /dev/null +++ b/codemp/x_ui/x_ui.vcproj @@ -0,0 +1,361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codemp/zlib32/deflate.cpp b/codemp/zlib32/deflate.cpp new file mode 100644 index 0000000..0301b6a --- /dev/null +++ b/codemp/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/codemp/zlib32/deflate.h b/codemp/zlib32/deflate.h new file mode 100644 index 0000000..1f7d4a0 --- /dev/null +++ b/codemp/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/codemp/zlib32/inflate.cpp b/codemp/zlib32/inflate.cpp new file mode 100644 index 0000000..887bb23 --- /dev/null +++ b/codemp/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/codemp/zlib32/inflate.h b/codemp/zlib32/inflate.h new file mode 100644 index 0000000..550244d --- /dev/null +++ b/codemp/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/codemp/zlib32/vssver.scc b/codemp/zlib32/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..2a04f56c63f72f57a2d751513d0b744d56fe4257 GIT binary patch literal 128 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiY;YJ0xgMUVju?0|G(`!WanTJ028pol$?Z?QV$ z9lwA@3L}v30OWfro3rf>J)91bcLegwPO{woKJR!sNZtv^XY1>ARz7qJ%y$OzQ>y2_ KXkB;$%m)B>K_kQf literal 0 HcmV?d00001 diff --git a/codemp/zlib32/zip.h b/codemp/zlib32/zip.h new file mode 100644 index 0000000..1a9fd2d --- /dev/null +++ b/codemp/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/codemp/zlib32/zipcommon.cpp b/codemp/zlib32/zipcommon.cpp new file mode 100644 index 0000000..77f56cd --- /dev/null +++ b/codemp/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/ui/menudef.h b/ui/menudef.h new file mode 100644 index 0000000..5e8d147 --- /dev/null +++ b/ui/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/ui/vssver.scc b/ui/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..ce5e7bcd9cbc08fcf787d907bd1f8b4f11308ef6 GIT binary patch literal 48 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiYSN!<`MV`2aU2LT3#fCIK$mBo0o`GFz;cZ3Yt literal 0 HcmV?d00001

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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/SHDebug/vssver.scc b/code/SHDebug/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..1209876d2f892d8deff310eec53d1fa9f458522f GIT binary patch literal 64 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiWw8L#k2XJY_^3?Qwt@5G_)H#y}Qfg+heep)MA K=+iC}FdqP^pb~=s literal 0 HcmV?d00001 diff --git a/code/VU.bat b/code/VU.bat new file mode 100644 index 0000000..0eb318d --- /dev/null +++ b/code/VU.bat @@ -0,0 +1 @@ +VersionUpdate -ss_ini \\Ravendata1\VSS\central_code\SRCSAFE.INI -ss_prj $/Jedi/Code -header win32/AutoVersion.h -email SW-Everyone -interactive diff --git a/code/cgame/FX_ATSTMain.cpp b/code/cgame/FX_ATSTMain.cpp new file mode 100644 index 0000000..b60c493 --- /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..b89b9f0 --- /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..5f40aaa --- /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..caa4c6b --- /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..90ff22d --- /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..e6c98e9 --- /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..74dba47 --- /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..ba95237 --- /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..8e784b1 --- /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..61492b6 --- /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..59c194c --- /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..e390cc4 --- /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..35b8a7c --- /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..3f8b977 --- /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..0d9914a --- /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..2bfe9d1 --- /dev/null +++ b/code/cgame/FxPrimitives.cpp @@ -0,0 +1,2289 @@ +// 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) +static long myftol( float f ) +{ + static int tmp; + __asm fld f + __asm fistp tmp + __asm mov eax, tmp +} +#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.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 ); + + } + 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..2a40a5a --- /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..c89a905 --- /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..f66c48d --- /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..958dd47 --- /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..a9ee6e2 --- /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..9bc8cbd --- /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..2e3650d --- /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..37dfa81 --- /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..84bfc55 --- /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..c0a3e9a --- /dev/null +++ b/code/cgame/cg_camera.cpp @@ -0,0 +1,1955 @@ +//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 ); + +/* +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 = 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 ); + } + } + } +} +/* +------------------------- +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 ); +} + +/* +------------------------- +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; + + CG_FillRect( cg.refdef.x, cg.refdef.y, 640, client_camera.bar_height, modulate ); + CG_FillRect( cg.refdef.x, cg.refdef.y + 480 - client_camera.bar_height, 640, client_camera.bar_height, modulate ); + } + + //NOTENOTE: Camera always draws the fades unless the alpha is 0 + if ( client_camera.fade_color[3] == 0.0f ) + return; + + 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 +} + +/* +------------------------- +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..b25ff71 --- /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..b58298f --- /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..12eebbf --- /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..9a39b3c --- /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..6943365 --- /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..6110dd2 --- /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..d754799 --- /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..68000db --- /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..38959d8 --- /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..bd89210 --- /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..dfeceea --- /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..e1ed622 --- /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..155d416 --- /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..4697b28 --- /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..fe97302 --- /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..c91c152 --- /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..b24db3f --- /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..9a27cb3 --- /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..e639718 --- /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..096ed01 --- /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..e121023 --- /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..3d854bf --- /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..1d48317 --- /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..df8bc38 --- /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/IFC/vssver.scc b/code/ff/IFC/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..00bae1282ea279c6f415757145e34ea64732e09e GIT binary patch literal 480 zcmW;ITPVW;6b5h`$<|UPg=QitnU$i{rcu0DYLSvHQWIe-i#>=$6S>roR!nVKXkxRu z%*M>+Ki6WJJ&Z||+?M8H(;mjzIWOn?bzZ(s5uFqxC+IEXua*j(FGs{|4z08X`D{a6 z4306nzY~&EA9;Tyh#TO$f>76yz!izw1fHzo&Wmb(&}Oh&lPEQ{jiNWf)9(st`jiU2 z1^$|IwGU-3ptr$n%BozMyM*2WUq4ACj5Isi0#+86<|{@j(YxSA@8V!5trl$shebAx z+uDG(frnT#>Njymgbe-mz)8lnGJ#))_kHlNM0Pge=|S7Uu66TAcT+6-06frF-upmM z(GKvwi$heUq@fSN%LXH}o|}z60*hz=;vM=t^fCB((;f6w7otzVD(V1}2f zuhm_UqW^P($1(z1PQeZO6wEv5zh4wSqR+t2Lw=Tj`4fE(CMzj{I#vLE0oKu`UxcAB O`VxHWyZRl_D$xJ`v6|EX literal 0 HcmV?d00001 diff --git a/code/ff/cl_ff.cpp b/code/ff/cl_ff.cpp new file mode 100644 index 0000000..6becc34 --- /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..fed1963 --- /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..476b77c --- /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..fefab0a --- /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..8321c1b --- /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..1b9acda --- /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..0f6f7d3 --- /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..022d940 --- /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..f8dd946 --- /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..18b8d88 --- /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..b75f82b --- /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..c8171f4 --- /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..42eca88 --- /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..466794f --- /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..5f9107d --- /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..a7594e2 --- /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..5041147 --- /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..b42cd99 --- /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..fe413c1 --- /dev/null +++ b/code/ff/ff_console.cpp @@ -0,0 +1,248 @@ +/* + * 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}; // 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: + case fffx_Laser2: + case fffx_Laser3: + 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 = IN_GetMainController(); + if(const_rumble[client] == -1) + { + const_rumble[client] = IN_CreateRumbleScript(IN_GetMainController(), 9, true); + if (const_rumble[client] != -1) + { + IN_AddEffectFade4(const_rumble[client], 0,0, 50000, 0, 2000); + 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 = IN_GetMainController(); + 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(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); + } +} + diff --git a/code/ff/ff_ffset.cpp b/code/ff/ff_ffset.cpp new file mode 100644 index 0000000..8b5065b --- /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..1b34aab --- /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..7488e69 --- /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..96e2a1e --- /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..87f7112 --- /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..c0cb1dd --- /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..7e5e18e --- /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..73c24c4 --- /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..1cb87ce --- /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..24fa14c --- /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/ff/vssver.scc b/code/ff/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..5fadef8e619f00ef3bf4dbbe076408d0f34b8f3a GIT binary patch literal 496 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiXDy0K1U5(@(uOa{`ikyoCFlnMV~1d2=n^6z|@ zdiPMfGMGOV$d~7x8+l=!3Yb3)$k&&SJN~Rt2h5)i{w!_RyRQFCK>hQ8{JAqvczbM|43?h{O^{2-N5!O1@e;( z@++Q->jll02J_bf`O!D+)e{!Ufcfiy d{LOrx|H@od!2Dbh28O(<$qf}v|NaA|7y!UDo=^Y) literal 0 HcmV?d00001 diff --git a/code/game/AI_Animal.cpp b/code/game/AI_Animal.cpp new file mode 100644 index 0000000..8bc3717 --- /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..07105ee --- /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..3d77729 --- /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..b462a5e --- /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..4715ef7 --- /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..67a4dba --- /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..c38efff --- /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_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 ) + { + // 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..250d6ca --- /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..784d33d --- /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..26bf55c --- /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..45f8cda --- /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..c7227a8 --- /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..edf76b0 --- /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..37486a7 --- /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..ec59a0b --- /dev/null +++ b/code/game/AI_Jedi.cpp @@ -0,0 +1,7609 @@ +// 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, angles; + + 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..dff596f --- /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..c853603 --- /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..c00006d --- /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..fea3c4f --- /dev/null +++ b/code/game/AI_Rancor.cpp @@ -0,0 +1,1684 @@ +// 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 != 113811381138 ) + { + 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 == 113811381138 ) + {//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 ); + 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..400981f --- /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..5b68a70 --- /dev/null +++ b/code/game/AI_RocketTrooper.cpp @@ -0,0 +1,908 @@ +#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; + } + } + 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; + } + } +} + +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..03c49cc --- /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..f4ffde6 --- /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..489b81e --- /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..69ec9b1 --- /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..4aad139 --- /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..149c8c4 --- /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..204ced7 --- /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..ed3d098 --- /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..1c2b1a1 --- /dev/null +++ b/code/game/AI_Wampa.cpp @@ -0,0 +1,904 @@ +// 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_WAIST; + if ( g_dismemberment->integer != 113811381138 ) + { + 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_WAIST; + if ( g_dismemberment->integer != 113811381138 ) + { + 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..124dc5e --- /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/FighterNPC.c b/code/game/FighterNPC.c new file mode 100644 index 0000000..baf3bc8 --- /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..65e8f46 --- /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..a51a1f2 --- /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 != 113811381138 && !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..13001cf --- /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..3ec1b5e --- /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..ed45184 --- /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..bb1a1f9 --- /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..1ef0b04 --- /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..ec4bf3c --- /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..fa17fd4 --- /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..cb25924 --- /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..91c0d55 --- /dev/null +++ b/code/game/NPC_spawn.cpp @@ -0,0 +1,4376 @@ +//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 +*/ +extern cvar_t *g_char_model; +void SP_NPC_Jedi( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( self->spawnflags & 4 ) + {//random! + int sanityCheck = 20; //just in case + while ( sanityCheck-- ) + { + switch( Q_irand( 0, 11 ) ) + { + case 0: + self->NPC_type = "jedi_hf1"; + break; + case 1: + self->NPC_type = "jedi_hf2"; + break; + case 2: + self->NPC_type = "jedi_hm1"; + break; + case 3: + self->NPC_type = "jedi_hm2"; + break; + case 4: + self->NPC_type = "jedi_kdm1"; + break; + case 5: + self->NPC_type = "jedi_kdm2"; + break; + case 6: + self->NPC_type = "jedi_rm1"; + break; + case 7: + self->NPC_type = "jedi_rm2"; + break; + case 8: + self->NPC_type = "jedi_tf1"; + break; + case 9: + self->NPC_type = "jedi_tf2"; + break; + case 10: + self->NPC_type = "jedi_zf1"; + break; + case 11: + default://just in case + self->NPC_type = "jedi_zf2"; + break; + } + if ( strstr( self->NPC_type, g_char_model->string ) != NULL ) + {//bah, we're using this one, try again + continue; + } + break; //get out of the while + } + } + 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..47e8c2e --- /dev/null +++ b/code/game/NPC_stats.cpp @@ -0,0 +1,4014 @@ +//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>2) // 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")); + + // 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..0333019 --- /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..a153a03 --- /dev/null +++ b/code/game/Q3_Interface.cpp @@ -0,0 +1,11277 @@ +// 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))) + { + 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..98c7a2f --- /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..50e0664 --- /dev/null +++ b/code/game/SpeederNPC.c @@ -0,0 +1,1180 @@ +// 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; + + 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 + +// 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/code/game/WalkerNPC.c b/code/game/WalkerNPC.c new file mode 100644 index 0000000..92df1b7 --- /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..8e3f758 --- /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..7fb3e15 --- /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..4adec3b --- /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..cbaad9c --- /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..85d34d0 --- /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..6f4b5b9 --- /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..6f52773 --- /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..7bc0948 --- /dev/null +++ b/code/game/bg_pangles.cpp @@ -0,0 +1,1842 @@ +// 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 ) +{ + 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..266101a --- /dev/null +++ b/code/game/bg_panimate.cpp @@ -0,0 +1,6639 @@ +// 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); + 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..34d8335 --- /dev/null +++ b/code/game/bg_pmove.cpp @@ -0,0 +1,15090 @@ +// 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 += gent->client->ps.forcePowerLevel[FP_SABER_OFFENSE]+Q_irand(0,g_spskill->integer)+Q_irand(0,1); + } + return strength; +} + +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..3d04738 --- /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 8 +#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..2b9a3f9 --- /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..865da19 --- /dev/null +++ b/code/game/bg_vehicleLoad.c @@ -0,0 +1,1668 @@ +//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 + +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..0fdfb60 --- /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..b4f5f25 --- /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..8d3344c --- /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..bc4af40 --- /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..b22c828 --- /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..c66a87a --- /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..ed4973c --- /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..289c2e5 --- /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..18901e0 --- /dev/null +++ b/code/game/g_active.cpp @@ -0,0 +1,5780 @@ +// 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.95 ); + } +} + +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 ); + +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 ) + { + // 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 ); +} + +/* +================== +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..8f4b66c --- /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..efea818 --- /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..ae848ac --- /dev/null +++ b/code/game/g_client.cpp @@ -0,0 +1,2416 @@ +// 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; + } +} + + + +/* +============ +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( "(null)", saber0Name ) != 0 ) + { + ent->client->ps.saber[0].name = G_NewString( saber0Name ); + } + if ( saber1Name[0] && Q_stricmp( "(null)", saber1Name ) != 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..f65ad99 --- /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..6e17709 --- /dev/null +++ b/code/game/g_combat.cpp @@ -0,0 +1,6945 @@ +// 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_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 == 113811381138 || !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_Dismemberable( ent, *hitLoc ) ) + {//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 != 113811381138 && g_saberRealisticCombat->integer < 2 ) + { + if ( 1 ) //g_dismemberProbabilities->value > 0.0f == it always is now, no cheating and changing the game from what was approved. + {//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 ( Q_flrand( 1, 100 ) > dismemberProb*2.0f )//probabilities seemed really really low, had to crank them up + { + 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 == 113811381138 +#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 ) ) + {//we're using probabilities + //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..eb6ec5c --- /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.95 ); + } + + 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.95 ); + } + // 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..8e11517 --- /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..79f27e0 --- /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..0e2ce36 --- /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..fe27977 --- /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..f0c009d --- /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..d5e4c5b --- /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..716a477 --- /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..9522506 --- /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..3efaf18 --- /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 (void); +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..4d6c9dd --- /dev/null +++ b/code/game/g_main.cpp @@ -0,0 +1,2141 @@ +// 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 "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]; +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 + // 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_saberMoreRealistic", "0", CVAR_CHEAT|CVAR_INIT );//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 +============= +*/ +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 ) + { + 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_RunFrame + +Advances the non-player objects in the world +================ +*/ +#if AI_TIMERS +int AITime = 0; +int navTime = 0; +#endif// AI_TIMERS + + +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); + } + + + 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(); +#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(); +} diff --git a/code/game/g_mem.cpp b/code/game/g_mem.cpp new file mode 100644 index 0000000..57345d8 --- /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..26c04bf --- /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.95 ); + } + } + 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..e62f282 --- /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..ab225ac --- /dev/null +++ b/code/game/g_missile.cpp @@ -0,0 +1,1534 @@ +// 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 + +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..16cd892 --- /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..183159e --- /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..b4dc0ab --- /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..ecb57ae --- /dev/null +++ b/code/game/g_navigator.cpp @@ -0,0 +1,5508 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// 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 = 1024, // 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 +//////////////////////////////////////////////////////////////////////////////////// +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)) + ) + { + return false; + } + + // 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) +{ + TEntEdgeMap::iterator i = mEntEdgeMap.begin(); + for ( ; i != mEntEdgeMap.end(); i++) + { + i->clear(); + } + mEntEdgeMap.clear(); +} + + + + + + + diff --git a/code/game/g_navigator.h b/code/game/g_navigator.h new file mode 100644 index 0000000..532dcb1 --- /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..9f6083a --- /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..34c16af --- /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..9ad2ec2 --- /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..7dd183b --- /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..ed8db5c --- /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..8487fbd --- /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..c04f531 --- /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..5d471c6 --- /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..0a6e53e --- /dev/null +++ b/code/game/g_savegame.cpp @@ -0,0 +1,1272 @@ +// 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; + + 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 \ No newline at end of file diff --git a/code/game/g_session.cpp b/code/game/g_session.cpp new file mode 100644 index 0000000..e933259 --- /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..21178c9 --- /dev/null +++ b/code/game/g_shared.h @@ -0,0 +1,930 @@ +#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]; +#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..910f833 --- /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..238fb7c --- /dev/null +++ b/code/game/g_svcmds.cpp @@ -0,0 +1,1342 @@ +// 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..9326c18 --- /dev/null +++ b/code/game/g_target.cpp @@ -0,0 +1,1234 @@ +// 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 ); + } + } +} + +void target_autosave_use(gentity_t *self, gentity_t *other, gentity_t *activator) +{ + 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..d6180c9 --- /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..8f01558 --- /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..dce243f --- /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..52747db --- /dev/null +++ b/code/game/g_utils.cpp @@ -0,0 +1,2149 @@ +// 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. +================= +*/ +gentity_t *G_Spawn( void ) +{ + 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 + 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 +================= +*/ +void G_FreeEntity( gentity_t *ed ) { + 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) + { + 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 + { + 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..4d82f86 --- /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..c3fcf4a --- /dev/null +++ b/code/game/g_vehicles.c @@ -0,0 +1,3196 @@ +// 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.86 ); //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 +} + +// 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 ); +#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..0b12152 --- /dev/null +++ b/code/game/g_vehicles.h @@ -0,0 +1,624 @@ +#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; +}; + +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..f6435f7 --- /dev/null +++ b/code/game/g_weapon.cpp @@ -0,0 +1,5357 @@ +// 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 (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; + + 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 + { + 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 )) + { + // 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 > 9 ) + { + 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 10 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; + } +} \ No newline at end of file diff --git a/code/game/g_weaponLoad.cpp b/code/game/g_weaponLoad.cpp new file mode 100644 index 0000000..7096d83 --- /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 > 64) + { + len = 64; + 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..d405f05 --- /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..34a11a6 --- /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..794f014 --- /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..fa61fd7 --- /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..e0975d9 --- /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..ca5a506 --- /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..a4d64c2 --- /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/mssccprj.scc b/code/game/mssccprj.scc new file mode 100644 index 0000000..b1297bc --- /dev/null +++ b/code/game/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[game.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/code/game", IVBAAAAA diff --git a/code/game/npc_headers.h b/code/game/npc_headers.h new file mode 100644 index 0000000..0ad76eb --- /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..4983147 --- /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..2eaeb1a --- /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..2b9c011 --- /dev/null +++ b/code/game/q_shared.h @@ -0,0 +1,2419 @@ +#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 +#define MAX_OSPATH 260 // 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 + +// 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 _XBOX /// 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 _XBOX + __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 _XBOX + __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 _XBOX +__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 _XBOX + 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 _XBOX + 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 + +// 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..0d47401 --- /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..2b7d3ff --- /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..a439ec3 --- /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..3721cdd --- /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/vssver.scc b/code/game/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..3cc7cbe1c347d71931abfbd00ad0c107fda8570b GIT binary patch literal 2272 zcmW;Nc~nzp8VBH;dvmj0MF?Pk04gW~0V|uiJBra%y5^@C#V`x!;*zB03rTq}uSfa`Qsn zK;kGLZ--S0t?zZ+T4pig9q@1Yr(=tfZZRZ3FX7?3DelYN)0t?v6aMvwhzCygE{p(v z1y_tYUO8O4n0FU!cBC%dCbN+?MZ6mxJ6&eBJ-V0ngL`0yd~XNUSQgz3_rgkbh4a?X zDmoPn_Q8QB`)jJwf@v?r|A4dSM&-wj#L*V;8(5(WJhwW6rAhy9;dcp({EnZI(=_}J zjx$s|*p_3$n8614jmden`8GSo67GjPlXZ_94%yOVeFxxF*Uf()PWg;)?>)?@jk$4Z zK27pD2-^ks-)VVK$CCbr;7sS&qlS*ByoX`?&czEOl3H1^o+I$dJ6%cDv6)g*{sUa> zVe`cDyq}b`Hwq`_ou9YYC76$o!IQhr*=D3}kaCER!!=z;KCHWu&-TF|;jQLQ69V@V z)*b!?OE&2(mf2op$$Tf^<7#H<_KP|+Lp(q;ONOxDMId9ekW?mhlfhR^=4%O8w8v-ub39B; z@?ixR{#+Uw5d0lqUkNV^PYM_Gio_(J)^MuFy_RJ|a`6<{2A*oSdA%ylg0F82S30EB zwf^bB*Pj6|YYW{OIq#u})Sn3(p4a?d+S)8~hG)U!S(pBCNH-!1hwb3@0PWfGEeAxd zusy6Dz1XvtDH4(V%!WhWB?o1!yeJ~$p98Nu;nzFdep_S=JHXc?4^~{VUMEt(bKxd! zZ?z#_!ou8`Y#3-l6i*dMm&?`mnH+a=`u1i-Jh@AIn6Ge}5&1K|>n z`&L1pN_Ypsxo;*)tKF|jW*{C6AITNQo6Pi;ko^?`_diPTEwYG`OoKz=OOeyQVfShz zI6BmF_~w^E1+#vdBhkZQu<7ju2Bqu~w+CJUA8z~QhGtO*NA^!R+?-#2|CY-LNA~wh z*sF7!`F8)qeEC)Ii716K&gn8Y5Ag_?b+l0asPiF5f%+DY`ej19oWV65xvwMP_$3`r zUx)4Cg5WjqjuAz{AzH?f`KsZY0cA~XZZmjC!NT~O0!wQZN5;Pv?yaI1spA)NMsPHI zd9!DTX;!0*(Ut1gne|blGpdBAX3A!Y0su z@%gTfznO~2{x`t7pt!`^%jNufy@yj02K(F8H~9PPI4+f3Hk8{p%up*({uZqN!@Kyc zjXy))2mgaDpDb~AE65O#`}`hUZOCo6FP=l$!b?#oBy&|rUrjAT)+-qPp(WX5pJT9q zj6VdfOnI`i_1Gc-$ww&s?zvE~qhiT=zci<) zYi!(+gdH)A8{7%6Zg6Y+Mw>>H`|1_EBHaIjBFbB|9`1%?IOf-kXVo$?zFs&&`0)O{ uGAGf0;j_~z$}Pn@ZmM`ApTCE&l{jaGrrE%5M7#msU)=DqHfcKN1OEk#dQ6D` literal 0 HcmV?d00001 diff --git a/code/game/weapons.h b/code/game/weapons.h new file mode 100644 index 0000000..f33263a --- /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..4433fa2 --- /dev/null +++ b/code/game/wp_saber.cpp @@ -0,0 +1,13660 @@ +// 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" + +#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 +} + +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 == 113811381138 || 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 ); + } + } + } + } + } + } + } + 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 ( 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 || hitEnt->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" + } + } + 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 <= 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 <= 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 ); + } + } + + //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 ); + } + } + // 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 ( 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 ); + } + } + else + { + g_saberFlashTime = level.time-50; + if ( !g_saberNoEffects ) + { + G_PlayEffect( "saber/saber_cut", g_saberFlashPos, hitNorm ); + } + } +#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->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 ); + 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 ); + 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 ( 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 ); + } + } + } + } +} + +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; +} + +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 + 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; + } + } +} + +void WP_ForcePowerDrain( gentity_t *self, forcePowers_t forcePower, int overrideAmt ) +{ + if ( self->NPC ) + {//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; + } + } +} \ No newline at end of file diff --git a/code/game/wp_saber.h b/code/game/wp_saber.h new file mode 100644 index 0000000..a1a7b34 --- /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..b029095 --- /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,newFrame; + float lerp; + 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..58f2a94 --- /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..b86cf52 --- /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/ghoul2/vssver.scc b/code/ghoul2/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..7ffc18ebc97255facd48fcaeb0030b8050fc7bbb GIT binary patch literal 144 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZFG+mmJ#L55$$w2z=x{8#U+nu>Mfg&kD{#vQ- zbnT5XT(UrZDv-ZnpUjo>3AtR1KznM e?+tOAb<+AMFB_1b3FK>D;4|W8yU5E8 + * + * 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..d975935 --- /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..038a4fb --- /dev/null +++ b/code/goblib/goblib.vcproj @@ -0,0 +1,461 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/goblib/goblib.vcproj.vspscc b/code/goblib/goblib.vcproj.vspscc new file mode 100644 index 0000000..c231424 --- /dev/null +++ b/code/goblib/goblib.vcproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "relative:goblib" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT" +} diff --git a/code/goblib/mssccprj.scc b/code/goblib/mssccprj.scc new file mode 100644 index 0000000..af6c0e7 --- /dev/null +++ b/code/goblib/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[goblib.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/code/goblib", AAIAAAAA diff --git a/code/goblib/vssver.scc b/code/goblib/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..1a9136246317063fd0541e3e1364f5dc207e5444 GIT binary patch literal 96 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiW+>27UP5M=-ZMIinBjMGQk*-v;Gfg(ykzR2Xq m$5(6o2J@AH{5;#`ezAM|c$tCn>f#Ix4^-d%Wj9*@mInZr7aJG= literal 0 HcmV?d00001 diff --git a/code/icarus/BlockStream.cpp b/code/icarus/BlockStream.cpp new file mode 100644 index 0000000..f6fc13e --- /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..907ab29 --- /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..74d7e87 --- /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..8a10f87 --- /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..db3f2bf --- /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..51dcafb --- /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..b7086af --- /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..eb94a2a --- /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..66a49fb --- /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..b287902 --- /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..1b7d269 --- /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..283db55 --- /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/icarus/vssver.scc b/code/icarus/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..04c37f1e9983a729c0fbcd403ebe4776bad592ab GIT binary patch literal 224 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZ7rS=}lVr2k>9zh0%dHu;gx7XD1ssKg$fP531 z(3Ei1t-S0&em{`^;iu>4%d)<_AT2Y1{3oFme^hsKf#qic`9@cpL(|*E!1A+z{Ik37 zN8P=m3YMP@E1)k`GNcsK>q(lg42#a%Hajcp9J!mXH45@{Pr*p6Oa!8AgVzW literal 0 HcmV?d00001 diff --git a/code/jpeg-6/jcapimin.cpp b/code/jpeg-6/jcapimin.cpp new file mode 100644 index 0000000..3a28d01 --- /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..464f9b2 --- /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..4454de1 --- /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..0a8b1cc --- /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..2950660 --- /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..0a81d54 --- /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..b41a246 --- /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..1268802 --- /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..79e725e --- /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..ae4b3c1 --- /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..40de85f --- /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..187ecfc --- /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..990cb30 --- /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..ed9d0ac --- /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..323e862 --- /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..3cb4a96 --- /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..1e98fb2 --- /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..f4aafd7 --- /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..71390df --- /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..c7f3506 --- /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..8e27693 --- /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..611330a --- /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..eca1395 --- /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..1d66d4f --- /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..303c80e --- /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..2a20fe1 --- /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..65f3054 --- /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..db93ccc --- /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..a821d58 --- /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..fddf7ba --- /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..69d1edd --- /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..fab5534 --- /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..9eb2e27 --- /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..c60cba7 --- /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..9621d02 --- /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..0ffb8b4 --- /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..091fc8f --- /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..523dc45 --- /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..99f535d --- /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..048f2c0 --- /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..f9595e5 --- /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..0c7d7c1 --- /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..08f332a --- /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..b3b6a6d --- /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..3027b12 --- /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..9255505 --- /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..02083ac --- /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/jpeg-6/vssver.scc b/code/jpeg-6/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..eaad51d7a828325367e1a0440a5888c0a8ada21d GIT binary patch literal 784 zcmW;IT}TsP6b4|ISj41lTJ~dBHisE$gH~eTT%hwJ#S1A5qhYo(BP`QurLJ_Y=~|nq zna!bBf8SYS|_{TK)e5|*Y-R;*M4(ZV$&nY}yT&EYu*-oyC>5sZ*4?<1t@=@;o8 zQ*kG@75FVxVa(~Oun>B3)RW(bi)V!Pa0WUz;p#SJT%AKiz6B@qY=MccBjjq>@upW3 zttlYaz|TzsR%z=6vI2H5oQ@hzsvy_Gb3J0lMkk-Fgm?NgB#q63tl+-eaD1NG)0a6x zR>3PG;Zlj$N3MhC%{*6SeSlmK$A*Z${NO9dYS>wNtTtDqC*OgYzR>hsp^OvE*T99^ zHHBAandH0hus`RbCyGPX!k+HV_|26uWF1^l?Edr4&nGv)lGL8VAB-vFdvIckU73?K z$PVT=!umjBS#sVOxe1;uJNIm8^;dE;?7qzS=+MrS@5Af<9W0F&_{c4=D|?PxV_GIZ zfG>H^zih}*kXzx{1O2;M%}wNo@DT5~BIcEWtcT~z$M#87R&pEc>XUuz?RSw4aN3?5 zYNuFCHp178<}BVwHu({}VAH?Vl;@Kl!&&RcWg~~qlG|ZT+Ole@sEEA^_nP1^=VJD+ z$?G(K0>^uIaZ8HVk~?6fTPhjXvB{mV*m7zwXJsq73zoAsjqbN5kB-`NBz+%Hp=>pjf N?>IUu@NM`@{txe^()<7b literal 0 HcmV?d00001 diff --git a/code/mac/MacGamma.c b/code/mac/MacGamma.c new file mode 100644 index 0000000..e468450 --- /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..a429950 --- /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..efb145e --- /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 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 +#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..b9ecdf8 --- /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..2f46cc1 --- /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..b9e3375 --- /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..da86397 --- /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..a195b48 --- /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..798c878 --- /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..9643aa3 --- /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..2836860 --- /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/mac/vssver.scc b/code/mac/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..1081f2fdc9b1e04d5c685665aabed65f1a3899da GIT binary patch literal 256 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiX<-Mw{h9V-JEtOwGaFDwr*>J&2lICW`9XibS;mLPgZX=ad=R*6Di7uZ08to3wEzGB literal 0 HcmV?d00001 diff --git a/code/mp3code/cdct.c b/code/mp3code/cdct.c new file mode 100644 index 0000000..556270f --- /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..a5da233 --- /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..5f75427 --- /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..a88d338 --- /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..48be054 --- /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..6e9537d --- /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..6277d4b --- /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..4a7d4c9 --- /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..f3b664d --- /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..e3cba5b --- /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..884abb1 --- /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..537a4b7 --- /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..8a18f0d --- /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..3131bd5 --- /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..26d7dd8 --- /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..c8751f9 --- /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..f7cfee4 --- /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..dda1881 --- /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..aaf1ad7 --- /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..10c21cf --- /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..496ff4a --- /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..3a526b1 --- /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..efd3729 --- /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..6e7ef24 --- /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..78ad088 --- /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..bfbe7cf --- /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..4f08dd7 --- /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..58f6ac1 --- /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..95f956a --- /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..e538229 --- /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/vssver.scc b/code/mp3code/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..14eef1c1e83de3b8937f7b0b047a223a3915fc10 GIT binary patch literal 528 zcmW;ITPTA86bJBcY7cu5w!RcKccbKBh*9Uc+T$2rO{HsJ)DZ3*t!CLj^ zFJJjWHp1dyRm@Y*2YDH8Oztjyo#V(Wu;AHMpDF1euflOoe3k9=AbAZ|)Vue+d5@CU zVNsxeR+e^(yaB%_6o;99WMmU;uWL$VO)2C}*qS%4bneU~Z^6CgO0lm_PTq!RwCbcH zu8_O~drM9)?aHdiyRbUoo3VZ}^4gz`3ROya+TZC3HyRX#X5$Yg{T)2inPht)q=V literal 0 HcmV?d00001 diff --git a/code/mp3code/wavep.c b/code/mp3code/wavep.c new file mode 100644 index 0000000..6ff6af2 --- /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/mssccprj.scc b/code/mssccprj.scc new file mode 100644 index 0000000..1329978 --- /dev/null +++ b/code/mssccprj.scc @@ -0,0 +1,9 @@ +SCC = This is a Source Code Control file + +[JediAcademy.sln] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/code", ROBAAAAA + +[starwars.vcproj] +SCC_Aux_Path = "\\ravendata1\vss\central_code" +SCC_Project_Name = "$/jedi/code", ROBAAAAA diff --git a/code/null/mac_net.c b/code/null/mac_net.c new file mode 100644 index 0000000..6232aff --- /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..30b84f7 --- /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..59a5797 --- /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..2973a8d --- /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..2e5d3d4 --- /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/null/vssver.scc b/code/null/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..5d84ae81dc0030664fb76213c3f9d168e43f04d7 GIT binary patch literal 112 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZJGA(~_mz4nw?g44Jx1adSh3AVh0!8iv`3-LT u@7&qefcX!A`~^`pw?j3zg82`D{MWwg45B~m1M?pN`RTnSrw-S-fcXFtFCriS literal 0 HcmV?d00001 diff --git a/code/png/png.cpp b/code/png/png.cpp new file mode 100644 index 0000000..66d4a47 --- /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..7a3580a --- /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/png/vssver.scc b/code/png/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..02b04cce2706704f248040c02f12c3def88557b4 GIT binary patch literal 64 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZJlQsH#L4W}aE&^%CpWhDuo|PcV1{ApjmMaxAlloc) + { + 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..a531f8c --- /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..29d6bb0 --- /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..1f9bb3a --- /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..872171f --- /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..e990c66 --- /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..70ae607 --- /dev/null +++ b/code/qcommon/cm_load_xbox.cpp @@ -0,0 +1,1249 @@ +// 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; +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); + + // load into heap + outputLump.load(stripName, "shaders"); + CMod_LoadShaders( outputLump.data, outputLump.len ); + R_LoadShaders(); + + strcpy(lmName, name); + outputLump.load(stripName, "lightmaps"); + R_LoadLightmaps( outputLump.data, outputLump.len, lmName); + + { + fileBase = NULL; + outputLump.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"); + + CMod_LoadPatches(verts.data, verts.len, + patches.data, patches.len, + num_surfs ); + 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); + + Lump flares; + flares.load(stripName, "flares"); + + R_LoadFlares(flares.data, flares.len); + } + + outputLump.load(stripName, "leafs"); + CMod_LoadLeafs (outputLump.data, outputLump.len); + + outputLump.load(stripName, "leafbrushes"); + CMod_LoadLeafBrushes (outputLump.data, outputLump.len); + + 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); + + 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); + + TotalSubModels += cmg.numSubModels; + + CM_InitBoxHull (); + + *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 ) ); + } + 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..ba759ea --- /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..652d3de --- /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..3ed08ba --- /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..4c2459d --- /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..b1c6979 --- /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..1ab6b97 --- /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..2e2538a --- /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..4f97432 --- /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..7709f90 --- /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..f85718e --- /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..d7b6b48 --- /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..b1ea0b5 --- /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..cf5c75c --- /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..5db2b5f --- /dev/null +++ b/code/qcommon/cm_trace.cpp @@ -0,0 +1,1244 @@ + +#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 + +#if 0 + // uncomment this to test against every leaf in the world for debugging +CM_TraceThroughTree( tw, local, node->children[0], p1f, p2f, p1, p2 ); +CM_TraceThroughTree( tw, local, node->children[1], p1f, p2f, p1, p2 ); +return; +#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 { + // 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; +#if 0 +CM_TraceThroughTree( tw, local, node->children[0], p1f, p2f, p1, p2 ); +CM_TraceThroughTree( tw, local, node->children[1], p1f, p2f, p1, p2 ); +return; +#endif + offset = tw->maxOffset; + 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..55589b5 --- /dev/null +++ b/code/qcommon/cmd.cpp @@ -0,0 +1,708 @@ +// 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 +============ +*/ +void Cmd_ExecuteString( const char *text ) { + + // execute the command line + Cmd_TokenizeString( text ); + if ( !Cmd_Argc() ) { + return; // no tokens + } + + // 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..402de50 --- /dev/null +++ b/code/qcommon/common.cpp @@ -0,0 +1,1580 @@ +// 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 + +#ifndef FINAL_BUILD +#include "platform.h" +#endif + +#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 + +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 + +#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 ); +//JLF +//void G_DemoFrame(); + +//============================================================================ + +#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, ... ) { + 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 +} + + +/* +================ +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 { + // 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(); +#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 ); + + 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 void G_DemoFrame(); + +// G_DemoFrame(); + 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 +} + +#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..49505d0 --- /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..0eb3a56 --- /dev/null +++ b/code/qcommon/files.h @@ -0,0 +1,118 @@ +#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 ); +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..32b8268 --- /dev/null +++ b/code/qcommon/files_common.cpp @@ -0,0 +1,588 @@ + +/***************************************************************************** + * 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]; +} + +#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..d93300e --- /dev/null +++ b/code/qcommon/files_console.cpp @@ -0,0 +1,1033 @@ + +#include "../game/q_shared.h" +#include "qcommon.h" +#include "files.h" +#include "../win32/win_file.h" +#include "../zlib/zlib.h" + + + +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..4deaa35 --- /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..8642d08 --- /dev/null +++ b/code/qcommon/fixedmap.h @@ -0,0 +1,149 @@ +#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; + } + + + 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..70badb5 --- /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..982a284 --- /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..15ea66f --- /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..a2ea2cf --- /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..32057ff --- /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..ec27b67 --- /dev/null +++ b/code/qcommon/qcommon.h @@ -0,0 +1,850 @@ +// 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_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); //returns size if true + +#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); +} + + +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__ \ No newline at end of file diff --git a/code/qcommon/qfiles.h b/code/qcommon/qfiles.h new file mode 100644 index 0000000..9b8bde5 --- /dev/null +++ b/code/qcommon/qfiles.h @@ -0,0 +1,633 @@ +#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? +//#define DRAWVERT_ST_SCALE 4096.0f +#define DRAWVERT_ST_SCALE 2048.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..19ebeb6 --- /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..13d4196 --- /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..5e77588 --- /dev/null +++ b/code/qcommon/stringed_ingame.cpp @@ -0,0 +1,1264 @@ +// 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" +// +////////////////////////////////////////////////// + + +#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" + +/////////////////////////////////////////////// +// +// some STL stuff... +#pragma warning ( disable : 4786 ) // disable the usual stupid and pointless STL warning +#include +#include +#include +#include +#include +using namespace std; + +typedef vector vStrings_t; +typedef vector vInts_t; +// +/////////////////////////////////////////////// + +cvar_t *se_language = NULL; +cvar_t *se_debug = NULL; +cvar_t *sp_leet = NULL; // kept as 'sp_' for JK2 compat. + +#define __DEBUGOUT(_string) OutputDebugString(_string) +#define __ASSERT(_blah) assert(_blah) + +typedef struct SE_Entry_s +{ + string m_strString; + string m_strDebug; // english and/or "#same", used for debugging only. Also prefixed by "SE:" to show which strings go through StringEd (ie aren't hardwired) + int m_iFlags; + + SE_Entry_s() + { + m_iFlags = 0; + } + +} 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() + { + Clear( SE_FALSE ); + } + + ~CStringEdPackage() + { + Clear( SE_FALSE ); + } + + mapStringEntries_t m_StringEntries; // needs to be in public space now + SE_BOOL m_bLoadDebug; // "" + // + // flag stuff... + // + vector m_vstrFlagNames; + map m_mapFlagMasks; + + void Clear( SE_BOOL bChangingLanguages ); + void SetupNewFileParse( LPCSTR psFileName, SE_BOOL bLoadDebug ); + SE_BOOL ReadLine( LPCSTR &psParsePos, char *psDest ); + LPCSTR ParseLine( LPCSTR psLine ); + int GetFlagMask( LPCSTR psFlagName ); + 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 ); + void AddFlagReference( LPCSTR psLocalReference, LPCSTR psFlagName ); + 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(); + + if ( !bChangingLanguages ) + { + // if we're changing languages, then I'm going to leave these alone. This is to do with any (potentially) cached + // flag bitmasks on the game side. It shouldn't matter since all files are written out at once using the build + // command in StringEd. But if ever someone changed a file by hand, or added one, or whatever, and it had a + // different set of flags declared, or the strings were in a different order, then the flags might also change + // the order I see them in, and hence their indexes and masks. This should never happen unless people mess with + // the .STR files by hand and delete some, but this way makes sure it'll all work just in case... + // + // ie. flags stay defined once they're defined, and only the destructor (at app-end) kills them. + // + m_vstrFlagNames.clear(); + m_mapFlagMasks.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, SE_BOOL bLoadDebug ) +{ + 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; + m_bLoadDebug = bLoadDebug; +} + + +// 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(); +} + + +// returns flag bitmask (eg 00000010b), else 0 for not found +// +int CStringEdPackage::GetFlagMask( LPCSTR psFlagName ) +{ + map ::iterator itFlag = m_mapFlagMasks.find( psFlagName ); + if ( itFlag != m_mapFlagMasks.end() ) + { + int &iMask = (*itFlag).second; + return iMask; + } + + return 0; +} + + +void CStringEdPackage::AddFlagReference( LPCSTR psLocalReference, LPCSTR psFlagName ) +{ + // add the flag to the list of known ones... + // + int iMask = GetFlagMask( psFlagName ); + if (iMask == 0) + { + m_vstrFlagNames.push_back( psFlagName ); + iMask = 1 << (m_vstrFlagNames.size()-1); + m_mapFlagMasks[ psFlagName ] = iMask; + } + // + // then add the reference to this flag to the currently-parsed reference... + // + mapStringEntries_t::iterator itEntry = m_StringEntries.find( va("%s_%s",m_strCurrentFileRef_ParseOnly.c_str(), psLocalReference) ); + if (itEntry != m_StringEntries.end()) + { + SE_Entry_t &Entry = (*itEntry).second; + Entry.m_iFlags |= iMask; + } +} + +// 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_FLAGS, psLine)) + { + // FLAGS FLAG_CAPTION FLAG_TYPEMATIC + // + LPCSTR psReference = GetCurrentReference_ParseOnly(); + if (psReference[0]) + { + static const char sSeperators[] = " \t"; + char sFlags[1024]={0}; // 1024 chars should be enough to store 8 flag names + strncpy(sFlags, psLine, sizeof(sFlags)-1); + char *psToken = strtok( sFlags, sSeperators ); + while( psToken != NULL ) + { + // psToken = flag name (in caps) + // + Q_strupr(psToken); // jic + AddFlagReference( psReference, psToken ); + + // read next flag for this string... + // + psToken = strtok( NULL, sSeperators ); + } + } + else + { + psErrorMessage = "Error parsing file: Unexpected \"" sSE_KEYWORD_FLAGS "\"\n"; + } + } + 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; +} + +LPCSTR Leetify( LPCSTR psString ) +{ + static string str; + str = psString; + if (sp_leet->integer == 42) // very specific test, so you won't hit it accidentally + { + static const + char cReplace[]={ 'o','0','l','1','e','3','a','4','s','5','t','7','i','!','h','#', + 'O','0','L','1','E','3','A','4','S','5','T','7','I','!','H','#' // laziness because of strchr() + }; + + char *p; + for (int i=0; i -> 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 bLoadDebug = SE_TRUE, 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, bLoadDebug, 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, bLoadDebug, 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 ) +{ + char sReference[256]; // will always be enough, I've never seen one more than about 30 chars long + assert(strlen(psPackageAndStringReference) < sizeof(sReference) ); + Q_strncpyz(sReference, psPackageAndStringReference, sizeof(sReference) ); + Q_strupr(sReference); + + mapStringEntries_t::iterator itEntry = TheStringPackage.m_StringEntries.find( sReference ); + if (itEntry != TheStringPackage.m_StringEntries.end()) + { + SE_Entry_t &Entry = (*itEntry).second; + + if ( se_debug->integer && TheStringPackage.m_bLoadDebug ) + { + return Entry.m_strDebug.c_str(); + } + else + { + return Entry.m_strString.c_str(); + } + } + + // should never get here, but fall back anyway... (except we DO use this to see if there's a debug-friendly key bind, which may not exist) + // +// __ASSERT(0); + return ""; // you may want to replace this with something based on _DEBUG or not? +} + + +// convenience-function for the main GetFlags call... +// +int SE_GetFlags ( 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_GetFlags( sReference ); +} + +int SE_GetFlags ( LPCSTR psPackageAndStringReference ) +{ + mapStringEntries_t::iterator itEntry = TheStringPackage.m_StringEntries.find( psPackageAndStringReference ); + if (itEntry != TheStringPackage.m_StringEntries.end()) + { + SE_Entry_t &Entry = (*itEntry).second; + + return Entry.m_iFlags; + } + + // should never get here, but fall back anyway... + // + __ASSERT(0); + + return 0; +} + + +int SE_GetNumFlags( void ) +{ + return TheStringPackage.m_vstrFlagNames.size(); +} + +LPCSTR SE_GetFlagName( int iFlagIndex ) +{ + if ( iFlagIndex < TheStringPackage.m_vstrFlagNames.size()) + { + return TheStringPackage.m_vstrFlagNames[ iFlagIndex ].c_str(); + } + + __ASSERT(0); + return ""; +} + +// returns flag bitmask (eg 00000010b), else 0 for not found +// +int SE_GetFlagMask( LPCSTR psFlagName ) +{ + return TheStringPackage.GetFlagMask( psFlagName ); +} + +// I could cache the result of this since it won't change during app lifetime unless someone does a build-publish +// while you're still ingame. Cacheing would make sense since it can take a while to scan, but I'll leave it and +// let whoever calls it cache the results instead. I'll make it known that it's a slow process to call this, but +// whenever anyone calls someone else's code they should assign it to an int anyway, since you've no idea what's +// going on. Basically, don't use this in a FOR loop as the end-condition. Duh. +// +// Groan, except for Bob. I mentioned that this was slow and only call it once, but he's calling it from +// every level-load... Ok, cacheing it is... +// +vector gvLanguagesAvailable; +int SE_GetNumLanguages(void) +{ + if ( gvLanguagesAvailable.empty() ) + { + 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 + ); + + set strUniqueStrings; // laziness + LPCSTR p; + while ((p=SE_GetFoundFile (strResults)) != NULL) + { + LPCSTR psLanguage = TheStringPackage.ExtractLanguageFromPath( p ); + + // __DEBUGOUT( p ); + // __DEBUGOUT( "\n" ); + // __DEBUGOUT( psLanguage ); + // __DEBUGOUT( "\n" ); + + if (!strUniqueStrings.count( psLanguage )) + { + strUniqueStrings.insert( psLanguage ); + + // if english is available, it should always be first... ( I suppose ) + // + if (!stricmp(psLanguage,"english")) + { + gvLanguagesAvailable.insert( gvLanguagesAvailable.begin(), psLanguage ); + } + else + { + gvLanguagesAvailable.push_back( psLanguage ); + } + } + } + } + + return gvLanguagesAvailable.size(); +} + +// SE_GetNumLanguages() must have been called before this... +// +LPCSTR SE_GetLanguageName( int iLangIndex ) +{ + if ( iLangIndex < gvLanguagesAvailable.size() ) + { + return gvLanguagesAvailable[ iLangIndex ].c_str(); + } + + __ASSERT(0); + return ""; +} + +// SE_GetNumLanguages() must have been called before this... +// +LPCSTR SE_GetLanguageDir( int iLangIndex ) +{ + if ( iLangIndex < gvLanguagesAvailable.size() ) + { + return va("%s/%s", sSE_STRINGS_DIR, gvLanguagesAvailable[ iLangIndex ].c_str() ); + } + + __ASSERT(0); + return ""; +} + +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) +{ +#ifdef _XBOX // VVFIXME? +// extern void Z_SetNewDeleteTemporary(bool); +// Z_SetNewDeleteTemporary(true); +#endif + + TheStringPackage.Clear( SE_FALSE ); + +#ifdef _DEBUG +// int iNumLanguages = SE_GetNumLanguages(); +#endif + + se_language = Cvar_Get("se_language", "english", CVAR_ARCHIVE | CVAR_NORESTART); + se_debug = Cvar_Get("se_debug", "0", 0); + sp_leet = Cvar_Get("sp_leet", "0", CVAR_ROM ); + + // if doing a buildscript, load all languages... + // + extern cvar_t *com_buildScript; + if (com_buildScript->integer == 2) + { + int iLanguages = SE_GetNumLanguages(); + for (int iLang = 0; iLang < iLanguages; iLang++) + { + LPCSTR psLanguage = SE_GetLanguageName( iLang ); // eg "german" + Com_Printf( "com_buildScript(2): Loading language \"%s\"...\n", psLanguage ); + SE_LoadLanguage( psLanguage ); + } + } + + LPCSTR psErrorMessage = SE_LoadLanguage( se_language->string ); + if (psErrorMessage) + { + Com_Error( ERR_DROP, "SE_Init() Unable to load language: \"%s\"!\nError: \"%s\"\n", se_language->string,psErrorMessage ); + } + +#ifdef _XBOX +// Z_SetNewDeleteTemporary(false); +#endif +} + +void SE_ShutDown(void) +{ + TheStringPackage.Clear( SE_FALSE ); +} + + +// 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, SE_BOOL bLoadDebug /* = SE_TRUE */ ) +{ + 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, bLoadDebug ); + } + } + } + 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, SE_TRUE ); + if ( psErrorMessage ) + { + Com_Error( ERR_DROP, psErrorMessage ); + } + 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..107951c --- /dev/null +++ b/code/qcommon/stringed_ingame.h @@ -0,0 +1,110 @@ +// 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" +#define sSE_DEBUGSTR_PREFIX "[" // any string you want prefixing onto the debug versions of strings (to spot hardwired english etc) +#define sSE_DEBUGSTR_SUFFIX "]" // "" + +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_FLAGS "FLAGS" +#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_ShutDown ( void ); +void SE_CheckForLanguageUpdates(void); +int SE_GetNumLanguages ( void ); +LPCSTR SE_GetLanguageName ( int iLangIndex ); // eg "german" +LPCSTR SE_GetLanguageDir ( int iLangIndex ); // eg "strings/german" +LPCSTR SE_LoadLanguage ( LPCSTR psLanguage, SE_BOOL bLoadDebug = SE_TRUE ); +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); +// +// ditto... +// +int SE_GetFlags ( LPCSTR psPackageReference, LPCSTR psStringReference ); +int SE_GetFlags ( LPCSTR psPackageAndStringReference ); +// +// general flag functions... (SEP_GetFlagMask() return should be used with SEP_GetFlags() return) +// +int SE_GetNumFlags ( void ); +LPCSTR SE_GetFlagName ( int iFlagIndex ); +int SE_GetFlagMask ( LPCSTR psFlagName ); + + +// note that so far the only place in the game that needs to know these is the font system so it can know how to +// interpret char codes, for this reason I'm only exposing these simple bool queries... +// +inline SE_BOOL Language_IsRussian(void) +{ + return (se_language && !Q_stricmp(se_language->string, "russian")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsPolish(void) +{ + return (se_language && !Q_stricmp(se_language->string, "polish")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsKorean(void) +{ + return (se_language && !Q_stricmp(se_language->string, "korean")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsTaiwanese(void) +{ + return (se_language && !Q_stricmp(se_language->string, "taiwanese")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsJapanese(void) +{ + return (se_language && !Q_stricmp(se_language->string, "japanese")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsChinese(void) +{ + return (se_language && !Q_stricmp(se_language->string, "chinese")) ? SE_TRUE : SE_FALSE; +} + +inline SE_BOOL Language_IsThai(void) +{ + return (se_language && !Q_stricmp(se_language->string, "thai")) ? SE_TRUE : SE_FALSE; +} + + +#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..b23b9fa --- /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..b0d5cbb --- /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..9097b77 --- /dev/null +++ b/code/qcommon/tags.h @@ -0,0 +1,54 @@ +// 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(EVENT), + TAGDEF(CLIPBOARD), + 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), // general model usage), includes header-struct-only stuff like 'model_t' + 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(TEMP_TGA), // image workspace that's only temporary + TAGDEF(TEMP_JPG), // image workspace that's only temporary + TAGDEF(TEMP_PNG), // image workspace that's only temporary + TAGDEF(SND_MP3STREAMHDR), // specific MP3 struct for decoding (about 18..22K each?), not the actual MP3 binary + TAGDEF(SND_DYNAMICMUSIC), // in-mem MP3 files + TAGDEF(SND_RAWDATA), // raw sound data, either MP3 or WAV + TAGDEF(GHOUL2), // Ghoul2 stuff + TAGDEF(BSP), // guess. + TAGDEF(BSP_DISKIMAGE), // temp during loading, to save both server and renderer fread()ing the same file. Only used if not low physical memory (currently 96MB) + TAGDEF(GP2), // generic parser 2 + TAGDEF(SPECIAL_MEM_TEST), // special usage in one function only!!!!!! + TAGDEF(ANIMATION_CFG), // may as well keep this seperate / readable + + TAGDEF(SAVEGAME), // used for allocating chunks during savegame file read + TAGDEF(SHADERTEXT), // used by cm_shader stuff + TAGDEF(CM_TERRAIN), // terrain + TAGDEF(R_TERRAIN), // renderer side of terrain + TAGDEF(INFLATE), // Temp memory used by zlib32 + TAGDEF(DEFLATE), // Temp memory used by zlib32 + TAGDEF(POINTCACHE), // weather effects + TAGDEF(NEWDEL), +#ifdef _XBOX + TAGDEF(UI_ALLOC), + TAGDEF(BINK), +#endif + TAGDEF(COUNT) + +//////////////// eof ////////////// + diff --git a/code/qcommon/timing.h b/code/qcommon/timing.h new file mode 100644 index 0000000..c8ca8f7 --- /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..158678a --- /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..9fc6300 --- /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: Jmonroe $ + * $Revision: 5 $ + * $Modtime: 4/10/01 6:47p $ + * $Date: 4/10/01 7:28p $ + * + *****************************************************************************/ + +#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..0e46bbe --- /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/vssver.scc b/code/qcommon/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..8567cec0290b04d566604b87ec8529f1c05117eb GIT binary patch literal 848 zcmW;Kdq`7p6bJCTsikd}>D|5Pv{0J}R+L*lB4<7d6*I`pAm3$zQHrLPDHKNd05e1i z^{<>Sa$qUh(o#zsNCY;ArU`1OJ=}D@wyB$a@9+L`FQ0qP@Ao_B@RK?7sKR0A%&LWV zk&5XfNr%#nZNeDOv-5Toc^2^PkG=G(OyVTs;m;yG&{F50Y)8|4_&02rp`3feq$K_a zRt;F&Q>7syF8&uTj_hz(tA=Q9{Xf{(L(?;LOG`VzOYr&3M{`->I(iSRgZ;-#UryIr zXj@nh2WWW;Lvl4aZyC-kZOxIkHIV&Q;P#=k<-y!0VgvlibWT1lR?s$BzY2fws82k2 zvy<$%2Afx9bMbk7B!3;&G^Ut}&cu=PH(=LD^38DlPo=#}us!h>>=Vue|0q35OHj`?9QAzVlsGDg^kacv#E3sn z$sV&@y;j(}aCb~%Q^Ruo@4{d2>1A0tjVw1m3PW=19K2W%(99x4@nBa~otwi<54#`c z!(OjmUzQd`v)nvw@Jh0zw)ZPDSV!0v-f+5KknVGr-3!~n=A>@9p!WdT-vO>vcC>8g z4m0+M?}L4dN3F`?2}S@5U_Zm1Y_VvW;p!2>YZWKFL)B)6tH%+ZY(5sKmA_%Qe#)IF zYNEDJ*R?cD@~dIDl-7t4p(o2C{uH)E)($nl_hq^BYT$ei%g*8x8R`ES+?8mJaTy6E z^{u&3)Tmn4ShlHVx%;&a>)WmrY39dB{s#PZ<+j3@I8WxY3G=_54yxiANxl(28P`3T z^YH?)2@b5_t0LmUNPlK{{LA*kte|*?yPpq4m`Co&HA&$t&GlCT?_{fvH~CYfo^pnw NqBVZ8`PEjk-+$FB=o0_{ literal 0 HcmV?d00001 diff --git a/code/qcommon/z_memman_console.cpp b/code/qcommon/z_memman_console.cpp new file mode 100644 index 0000000..ac16c48 --- /dev/null +++ b/code/qcommon/z_memman_console.cpp @@ -0,0 +1,1843 @@ + +/* + * 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 +#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 +#ifdef _GAMECUBE +#define ZONE_HEAP_FREE_DEBUG (64*1024*4) +#define ZONE_HEAP_FREE_RELEASE (0) + +#ifdef _DEBUG +# define ZONE_HEAP_FREE ZONE_HEAP_FREE_DEBUG +#else +# define ZONE_HEAP_FREE ZONE_HEAP_FREE_RELEASE +#endif +#else +//Game needs about 8 MB for framebuffers audio, bink, etc., plus 17 MB (?) +//for textures. Leave lots more physical memory around when not in 64 MB +//map, so the profiler and other things work. +#ifdef FINAL_BUILD +# define ZONE_HEAP_FREE (1024*1024*8 + 17*1024*1024) +#else +# define ZONE_HEAP_FREE (1024*1024*16 + 16*1024*1024 + 16*1024*1024) +#endif +#endif + +// 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 bool s_IsNewDeleteTemp = false; + +#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); + + +#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; + s_IsNewDeleteTemp = false; + + // Alloc the pool +#if defined(_XBOX) + 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); + 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); +#elif defined(_WINDOWS) + SIZE_T size = 50*1024*1024; + s_PoolBase = GlobalAlloc(0, size); +#endif + + // 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); + +#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 || +#ifndef _JK2MP + eTag == TAG_TEMP_SAVEGAME_WORKSPACE || + eTag == TAG_STRING || +#endif +// eTag == TAG_TEMP_SND_RAWDATA || + eTag == TAG_SND_RAWDATA || + eTag == TAG_ICARUS || + eTag == TAG_LISTFILES || +#ifdef _JK2MP + eTag == TAG_TEXTPOOL; +#else + eTag == TAG_GP2; +#endif +// eTag == TAG_BINK; +} + +// Determine if a tag needs TagFree() support. +static bool Z_IsTagLinked(memtag_t eTag) +{ + return +// eTag == TAG_BOTGAME || + eTag == TAG_BSP || + eTag == TAG_HUNKALLOC || +#ifndef _JK2MP + eTag == TAG_HUNKMISCMODELS || + eTag == TAG_G_ALLOC || +#endif +#ifdef _JK2MP + eTag == TAG_CG_UI_ALLOC || + eTag == TAG_BG_ALLOC || +#endif + 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 && s_IsNewDeleteTemp) + { + eTag = TAG_TEMP_WORKSPACE; + } + + // 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 >= 0x1eb0000 && (unsigned)ablock <= 0x1ec0000 && iSize == 48) + { + 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) +{ + // Catch nested uses that break when unwinding the stack + assert(bTemp != s_IsNewDeleteTemp); + s_IsNewDeleteTemp = bTemp; +} + +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_GetTerrainMemory(void) +{ + return s_Stats.m_SizesPerTag[TAG_CM_TERRAIN] + + s_Stats.m_SizesPerTag[TAG_CM_TERRAIN_TEMP] + +#ifdef _JK2MP + s_Stats.m_SizesPerTag[TAG_TERRAIN] + +#endif + s_Stats.m_SizesPerTag[TAG_R_TERRAIN]; +} + +int Z_GetMiscMemory(void) +{ + return s_Stats.m_SizeAlloc - + (Z_GetLevelMemory() + +#ifdef _JK2MP + Z_GetHunkMemory() + +#endif + Z_GetTerrainMemory() + + 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); + + //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:\tTER:\tSND:\tTEX:\tFMV:\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, + (s_Stats.m_SizesPerTag[TAG_BINK] / 1000) + 1, + (Z_GetMiscMemory() / 1000) + 1, + s_Stats.m_SizeFree, + stat.dwAvailPhys)); + +#ifdef _JK2MP + Sys_Log("memory-map.txt", va("HUNK: %d, THUNK: %d\n", + (s_Stats.m_SizesPerTag[TAG_HUNKALLOC] / 1000) + 1, + (s_Stats.m_SizesPerTag[TAG_TEMP_HUNKALLOC] / 1000) + 1)); +#endif + + //Sys_Log("memory-map.txt", va("Free Zone: %d\n", s_Stats.m_SizeFree)); + +} + + +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) +{ + 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 + + 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 + +/* + 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) +{ + 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) +{ + 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..2c36f8e --- /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/renderer/amd3d.h b/code/renderer/amd3d.h new file mode 100644 index 0000000..45fece2 --- /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..22d1f95 --- /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..0220e94 --- /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..6ac4260 --- /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..50dacfe --- /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..543cf58 --- /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..cf48a7c --- /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..2fefbd3 --- /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..b693a6c --- /dev/null +++ b/code/renderer/tr_WorldEffects.cpp @@ -0,0 +1,2283 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// 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 ); + 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 ); + } + 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..038ae94 --- /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..d26dd28 --- /dev/null +++ b/code/renderer/tr_backend.cpp @@ -0,0 +1,1963 @@ +// 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/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 +================= +*/ +static 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 ); + +#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(); + + 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 = 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; + //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 +} + + +/* +============================================================================ + +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 + 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 + 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 ); + } + + 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..9d5fe55 --- /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 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; + } + +/* 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; + int 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]; + int 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; + 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 = (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; + 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",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..2736b9b --- /dev/null +++ b/code/renderer/tr_bsp_xbox.cpp @@ -0,0 +1,1666 @@ +// 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; + +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 int *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 + 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; + int 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; + int 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]; + } + + 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); + + 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; + + int 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; + int 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 ) +{ + gbUsingCachedMapDataRightNow = qtrue; // !!!!!!!!!!!! + + RE_LoadWorldMap_Actual( name, s_worldData, 0 ); + + gbUsingCachedMapDataRightNow = qfalse; // !!!!!!!!!!!! +} diff --git a/code/renderer/tr_cmds.cpp b/code/renderer/tr_cmds.cpp new file mode 100644 index 0000000..d2465c4 --- /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..ea574a2 --- /dev/null +++ b/code/renderer/tr_draw.cpp @@ -0,0 +1,1124 @@ +// tr_draw.c +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + + + +/* +============= +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 ) +{ + 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 +} + + + +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; + + if (Dissolve.pImage) + { + R_Images_DeleteImage( Dissolve.pImage ); + Dissolve.pImage = NULL; + } +} +// 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 = (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; +#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(32768,TAG_TEMP_WORKSPACE,qtrue); + + Dissolve.pImage = R_CreateImage( "*DissolveImage", // const char *name + data, // const byte *pic + 256, // int width + 256, // int height + GL_COMPRESSED_RGB_S3TC_DXT1_EXT, + qfalse, // qboolean mipmap + qfalse, // qboolean allowPicmip + GL_CLAMP // int glWrapClampMode + ); + Z_Free(data); + + GL_Bind(Dissolve.pImage); + + qglCopyBackBufferToTexEXT(256.0f, 256.0f, 0.0f, 0.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 + 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..a8e9140 --- /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..72f2952 --- /dev/null +++ b/code/renderer/tr_font.cpp @@ -0,0 +1,1714 @@ +// 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!!!!! +// +///////////////////////////////////////////////////////////////////////////////////////////////////////// + +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() +{ + 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... + // + 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 + { + 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; + } + } + +// // test code only +// if (GetLanguageEnum() == eTaiwanese) +// { +// psText = "Wp:¶}·F§a ¿p·G´µ¡A§Æ±æ§A¹³¥L­Ì»¡ªº¤@¼Ë¦æ¡C"; +// } +// else +// if (GetLanguageEnum() == eChinese) +// { +// //psText = "Ó¶±øÕ½³¡II Ô¼º²?ĪÁÖ˹ ÈÎÎñʧ°Ü ÄãÒªÌ×Óû­ÃæÉ趨µÄ±ä¸üÂ𣿠ԤÉè,S3 ѹËõ,DXT1 ѹËõ,DXT5 ѹËõ,16 Bit,32 Bit"; +// psText = "Ó¶±øÕ½³¡II"; +// } +// else +// if (GetLanguageEnum() == eThai) +// { +// //psText = "Áҵðҹ¼ÅÔµÀѳ±ìÍصÊÒË¡ÃÃÁÃËÑÊÊÓËÃѺÍÑ¡¢ÃÐä·Â·Õèãªé¡Ñº¤ÍÁ¾ÔÇàµÍÃì"; +// psText = "Áҵðҹ¼ÅÔµ"; +// psText = "ÃËÑÊÊÓËÃѺ"; +// psText = "ÃËÑÊÊÓËÃѺ ÍÒ_¡Ô¹_¤ÍÃì·_1415"; +// } +// else +// if (GetLanguageEnum() == eKorean) +// { +// psText = "Wp:¼îŸÀÓÀÌ´Ù ¸Ö¸°. ±×µéÀÌ ¸»ÇÑ´ë·Î ³×°¡ ÀßÇÒÁö ±â´ëÇÏ°Ú´Ù."; +// } +// else +// if (GetLanguageEnum() == eJapanese) +// { +// static char sBlah[200]; +// sprintf(sBlah,va("%c%c%c%c%c%c%c%c",0x82,0xA9,0x82,0xC8,0x8A,0xBF,0x8E,0x9A)); +// psText = &sBlah[0]; +// } +// else +// if (GetLanguageEnum() == eRussian) +// { +//// //psText = "Íà âåðøèíå õîëìà ñòîèò ñòàðûé äîì ñ ïðèâèäåíèÿìè è áàøíÿ ñ âîëøåáíûìè ÷àñàìè." +// psText = "Íà âåðøèíå õîëìà ñòîèò"; +// } +// else +// if (GetLanguageEnum() == ePolish) +// { +// psText = "za³o¿ony w 1364 roku, jest najstarsz¹ polsk¹ uczelni¹ i nale¿y..."; +// psText = "za³o¿ony nale¿y"; +// } + + + 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 '_': // 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/code/renderer/tr_font.h b/code/renderer/tr_font.h new file mode 100644 index 0000000..f46e611 --- /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..5e77015 --- /dev/null +++ b/code/renderer/tr_ghoul2.cpp @@ -0,0 +1,4374 @@ +// 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 "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 + Q_CastShort2FloatScale(&out->texCoords[baseVert][0][0], &texCoord->texCoords[0], 1.f / GLM_COMP_SIZE); + Q_CastShort2FloatScale(&out->texCoords[baseVert][0][1], &texCoord->texCoords[1], 1.f / GLM_COMP_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_SIZE); + Q_CastShort2FloatScale(out + 4, &texCoord->texCoords[1], 1.f / GLM_COMP_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); + } + + 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... ;-) +} + + +/*static inline*/ void UnCompressBone(float mat[3][4], int iBoneIndex, const mdxaHeader_t *pMDXAHeader, int iFrame) +{ + 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); + } + } + } + } +} + +// 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 +#ifndef VV_LIGHTING +// && RS.fogNum == 0 +#endif + && (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 = 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; + 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 ) + && !(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 ) + { // 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, 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); +} + +/* +============== +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 _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; +#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... + // + bAlreadyCached = qtrue; + assert( mdxm == buffer ); +// memcpy( mdxm, buffer, size ); // and don't do this now, since it's the same thing + + 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; +} + +/* +================= +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; + } + + 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); + + 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... + // + bAlreadyCached = qtrue; + assert( mdxa == buffer ); +// memcpy( mdxa, buffer, size ); // and don't do this now, since it's the same thing + + 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..7a48fa9 --- /dev/null +++ b/code/renderer/tr_image.cpp @@ -0,0 +1,3373 @@ +// 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" + + +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; +#ifdef _XBOX + 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; +#endif + default: + //"???? " + return 4; + } +} + +/* +=============== +R_SumOfUsedImages +=============== +*/ +#ifndef _XBOX +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; +} +#endif + +/* +=============== +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"}; + + VID_Printf (PRINT_ALL, "\n -w-- -h-- -fsK- -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); +// totalFileSizeK += (image->imgfileSize+1023)/1024; +#ifdef _XBOX + VID_Printf (PRINT_ALL, "%4i: %4i %4i %s ", + i, image->width, image->height, yesno[image->mipcount] ); +#else + //VID_Printf (PRINT_ALL, "%4i: %4i %4i %5i %s ", + // i, image->width, image->height,(image->fileSize+1023)/1024, yesno[image->mipmap] ); + VID_Printf (PRINT_ALL, "%4i: %4i %4i %s ", + i, image->width, image->height,yesno[image->mipmap] ); +#endif + 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; +#ifdef _XBOX + 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; +#endif + default: + VID_Printf( PRINT_ALL, "???? " ); + } + + switch ( image->wrapClampMode ) { + case GL_REPEAT: + VID_Printf( PRINT_ALL, "rept " ); + break; + case GL_CLAMP: + VID_Printf( PRINT_ALL, "clmp " ); + break; + case GL_CLAMP_TO_EDGE: + VID_Printf( PRINT_ALL, "clpE " ); + break; + default: + VID_Printf( PRINT_ALL, "%4i ", image->wrapClampMode ); + break; + } + +#ifndef _XBOX + VID_Printf( PRINT_ALL, "%s\n", image->imgName ); +#endif + i++; + } + VID_Printf (PRINT_ALL, " ---------\n"); + VID_Printf (PRINT_ALL, " -w-- -h-- -mm- -if- wrap --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 + +=============== +*/ +#ifdef _XBOX +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(); +} + +#else // _XBOX + +static void Upload32( unsigned *data, + GLenum format, + qboolean mipmap, + qboolean picmip, + qboolean isLightmap, + qboolean allowTC, + int *pformat, + USHORT *pUploadWidth, USHORT *pUploadHeight ) +{ + 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 (GL_TEXTURE_2D, 0, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + goto done; + } + + R_LightScaleTexture (data, width, height, !mipmap ); + + qglTexImage2D (GL_TEXTURE_2D, 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 (GL_TEXTURE_2D, miplevel, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + } + } + else + { + + } + +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, r_ext_texture_filter_anisotropic->value ); + } + } + else + { + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + GL_CheckErrors(); +} +#endif // _XBOX + +#ifdef _XBOX +typedef tmap (int, image_t *) AllocatedImages_t; + AllocatedImages_t* AllocatedImages = NULL; + AllocatedImages_t::iterator itAllocatedImages; +#else +class CStringComparator +{ +public: + bool operator()(const char *s1, const char *s2) const { return(stricmp(s1, s2) < 0); } +}; + +typedef map AllocatedImages_t; + AllocatedImages_t AllocatedImages; + AllocatedImages_t::iterator itAllocatedImages; +#endif // _XBOX +int giTextureBindNum = 1024; // will be set to this anyway at runtime, but wtf? + + +// return = number of images in the list, for those interested +// +#ifdef _XBOX +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; +} +#else +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; +} +#endif + +// 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"... +// +#ifdef _XBOX +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->isLightmap) + { + R_Images_DeleteImageContents(pImage); + + AllocatedImages->erase(itImage++); + bEraseOccured = qtrue; + } + } + + GL_ResetBinds(); +} + +#else // _XBOX + +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); + itImage = AllocatedImages.erase(itImage); + + bEraseOccured = qtrue; + } + } + + GL_ResetBinds(); +} +#endif // _XBOX + +// special function currently only called by Dissolve code... +// +#ifdef _XBOX +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); + } +} +#else // _XBOX + +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); + } +} +#endif // _XBOX + +// 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); + } + +#ifdef _XBOX + AllocatedImages->clear(); +#else + AllocatedImages.clear(); +#endif + + giTextureBindNum = 1024; +} + + +void RE_RegisterImages_Info_f( void ) +{ +#ifndef _XBOX + image_t *pImage = NULL; + int iImage = 0; + int iTexels = 0; + + int iNumImages = R_Images_StartIteration(); + while ( (pImage = R_Images_GetNextIteration()) != NULL) + { + VID_Printf( PRINT_ALL, "%d: (%4dx%4dy) \"%s\"",iImage, pImage->width, pImage->height, pImage->imgName); + VID_Printf( PRINT_ALL, ", levused %d",pImage->iLastLevelUsedOn); + VID_Printf( PRINT_ALL, "\n"); + + iTexels += pImage->width * pImage->height; + iImage++; + } + VID_Printf( PRINT_ALL, "%d Images. %d (%.2fMB) texels total, (not including mipmaps)\n",iNumImages, iTexels, (float)iTexels / 1024.0f / 1024.0f); + VID_Printf( PRINT_DEVELOPER, "RE_RegisterMedia_GetLevel(): %d",RE_RegisterMedia_GetLevel()); +#endif // _XBOX +} + + +// 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... +// +#ifdef _XBOX +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... + //VID_Printf( PRINT_DEVELOPER, "Dumping image \"%s\"\n",pImage->imgName); + R_Images_DeleteImageContents(pImage); + itImage = AllocatedImages->erase(itImage); + bEraseOccured = qtrue; + } + } + } + + GL_ResetBinds(); + + return bEraseOccured; +} + +#else // _XBOX +qboolean RE_RegisterImages_LevelLoadEnd(void) +{ + //VID_Printf( PRINT_DEVELOPER, "RE_RegisterImages_LevelLoadEnd():\n"); + + 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] != '*') + { + // image used on this level? + // + if ( pImage->iLastLevelUsedOn != RE_RegisterMedia_GetLevel() ) + { // nope, so dump it... + //VID_Printf( PRINT_DEVELOPER, "Dumping image \"%s\"\n",pImage->imgName); + R_Images_DeleteImageContents(pImage); + itImage = AllocatedImages.erase(itImage); + bEraseOccured = qtrue; + } + } + } + + //VID_Printf( PRINT_DEVELOPER, "RE_RegisterImages_LevelLoadEnd(): Ok\n"); + + GL_ResetBinds(); + + return bEraseOccured; +} +#endif // _XBOX + + + +// 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... +// +#ifdef _XBOX +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; +} + +#else // _XBOX + +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 ) { + 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 ); + } + if ( pImage->wrapClampMode != glWrapClampMode ) { + VID_Printf( PRINT_WARNING, "WARNING: reused image %s with mixed glWrapClampMode parm\n", pName ); + } + } + + pImage->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); + + return pImage; + } + + return NULL; +} +#endif // _XBOX + + + + +/* +================ +R_CreateImage + +This is the only way any image_t are created +================ +*/ +#ifdef _XBOX +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 ); + +// image->imgfileSize=0; + + 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)); + + 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; +} + +#else // _XBOX + +image_t *R_CreateImage( const char *name, const byte *pic, int width, int height, + GLenum format, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode,int fileSize) +{ + 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, mipmap, allowPicmip, allowTC, glWrapClampMode ); + if (image) { + return image; + } + + image = (image_t*) Z_Malloc( sizeof( image_t ), TAG_IMAGE_T, qtrue ); + + //image->imgfileSize=fileSize; + + 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 ); + } + + GL_Bind(image); + + Upload32( (unsigned *)pic, format, + image->mipmap, + allowPicmip, + isLightmap, + allowTC, + &image->internalFormat, + &image->width, + &image->height ); + + 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 + + LPCSTR psNewName = GenerateImageMappingName(name); + Q_strncpyz(image->imgName, psNewName, sizeof(image->imgName)); + AllocatedImages[ image->imgName ] = image; + + return image; +} +#endif // _XBOX + +void R_CreateAutomapImage( const char *name, const byte *pic, int width, int height, + qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) +{ +#ifdef _XBOX + R_CreateImage(name, pic, width, height, GL_RGBA, mipmap, allowPicmip, glWrapClampMode); +#else + R_CreateImage(name, pic, width, height, GL_RGBA, mipmap, allowPicmip, allowTC, glWrapClampMode); +#endif +} + +/* +========================================================= + +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 + +========================================================= +*/ + +#ifdef _XBOX + +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; +} + +#endif + +//=================================================================== + +/* +================= +R_LoadImage + +Loads any of the supported image types into a cannonical +32 bit format. +================= +*/ +#ifdef _XBOX +void R_LoadImage( const char *shortname, byte **pic, int *width, int *height, int *mipcount, GLenum *format ) { +#else +int R_LoadImage( const char *shortname, byte **pic, int *width, int *height, GLenum *format ) { +#endif + int bytedepth; + 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 ) ); + } +#ifdef _XBOX + *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; + } + + /* // Removing PNG support 2003/05/19 + COM_StripExtension(name,name); + COM_DefaultExtension(name, sizeof(name), ".png"); + + //No .tga existed, try .png + LoadPNG32( name, pic, width, height, &bytedepth ); + if (*pic) + { + return; + } + */ + + // No .png either, fall back to .dds + COM_StripExtension(name,name); + COM_DefaultExtension(name, sizeof(name), ".dds"); + LoadDDS( name, pic, width, height, mipcount, format ); + return; + +#else + *format = GL_RGBA; + COM_StripExtension(name,name); + COM_DefaultExtension(name, sizeof(name), ".jpg"); + + int fileSize; + //First try .jpg + fileSize=LoadJPG( name, pic, width, height ); + if (*pic) + { + return fileSize; + } + + COM_StripExtension(name,name); + COM_DefaultExtension(name, sizeof(name), ".png"); + + //No .jpg existed, try .png + fileSize=LoadPNG32( name, pic, width, height, &bytedepth ); + if (*pic) + { + return fileSize; + } + + COM_StripExtension(name,name); + COM_DefaultExtension(name, sizeof(name), ".tga"); + + //No .jpg existed and no .png existed, try .tga as a last resort. + fileSize=LoadTGA( name, pic, width, height ); + return fileSize; +#endif +} + + +#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 ); + +#ifndef _XBOX + if (!pic || !*pic) + { //png load failed, try jpeg + strcpy(work, name); + COM_DefaultExtension( work, sizeof( work ), ".jpg" ); + LoadJPG( work, pic, width, height ); + } +#endif + + 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. +============== +*/ +#ifdef _XBOX +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; +} + +#else // _XBOX + +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; +// long hash; + + 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, allowTC, glWrapClampMode ); + if (image) { + return image; + } + + // + // load the pic from disk + // + int fileSize=R_LoadImage( name, &pic, &width, &height, &format ); + if ( !pic ) { + return NULL; + } + + image = R_CreateImage( ( char * ) name, pic, width, height, format, mipmap, allowPicmip, allowTC, glWrapClampMode,fileSize ); + Z_Free( pic ); + return image; +} + +#endif // _XBOX + + +// EF dlight image creation code +/* +================ +R_CreateDlightImage +================ +*/ +/* +#define DLIGHT_SIZE 16 +static void R_CreateDlightImage( void ) { + 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] = 255; + data[y][x][3] = b/8; + } + } + tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, qfalse, qfalse, GL_CLAMP ); +} +*/ +// Holomatch dlight image creation code +/* +================ +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 > 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_TO_EDGE ); + qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + 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.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; + } + +#ifndef _XBOX + if ( glConfig.deviceSupportsGamma ) + { + GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); + } +#endif +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) { + //memset(hashTable, 0, sizeof(hashTable)); // DO NOT DO THIS NOW (because of image cacheing) -ste. +#ifdef _XBOX + if (!AllocatedImages) + { + AllocatedImages = new AllocatedImages_t; + } +#endif + + // 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; +} + +/* +class CStringComparator +{ +public: + bool operator()(const char *s1, const char *s2) const { return(stricmp(s1, s2) < 0); } +}; +*/ +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 +=============== +*/ +void R_UpdateSaveGameImage(const char* filename) +{ + // bind the savegame image + GL_Bind(tr.saveGameImage); + + // replace the texture with the one from the file + LoadCompressedScreenshot(filename); +} + +#endif // _XBOX + diff --git a/code/renderer/tr_init.cpp b/code/renderer/tr_init.cpp new file mode 100644 index 0000000..36edf4a --- /dev/null +++ b/code/renderer/tr_init.cpp @@ -0,0 +1,1626 @@ +// 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; +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 + 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..55efb88 --- /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..e5733c7 --- /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..b413740 --- /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..5506c38 --- /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_local.h b/code/renderer/tr_local.h new file mode 100644 index 0000000..032b767 --- /dev/null +++ b/code/renderer/tr_local.h @@ -0,0 +1,2170 @@ +#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; +} trRefEntity_t; + + +// 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 { +#ifdef _XBOX + int imgCode; +#else + char imgName[MAX_QPATH]; // game path, including extension + int frameUsed; // for texture usage in frame statistics +#endif + USHORT width, height; // source image +// int imgfileSize; + + GLuint texnum; // gl texture binding + int internalFormat; + int wrapClampMode; // GL_CLAMP or GL_REPEAT + +#ifdef _XBOX + bool isLightmap; + bool isSystem; + short mipcount; +#else + bool mipmap; +#endif + + 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; + + texCoordGen_t tcGen; + vec3_t *tcGenVectors; + + texModInfo_t *texMods; + short numTexMods; + short numImageAnimations; + float imageAnimationSpeed; + + bool isLightmap; + bool oneShotAnimMap; + bool vertexLightmap; + bool isVideoMap; + + int videoMapHandle; +} textureBundle_t; + +#define NUM_TEXTURE_BUNDLES 2 + +typedef struct { + bool active; + bool isDetail; +#ifdef _XBOX + byte isEnvironment; +#endif +#ifdef VV_LIGHTING + byte isSpecular; + byte isBumpMap; +#endif + byte index; // index of stage + byte lightmapStyle; + + textureBundle_t bundle[NUM_TEXTURE_BUNDLES]; + + waveForm_t rgbWave; + colorGen_t rgbGen; + + waveForm_t alphaWave; + alphaGen_t alphaGen; + + byte constantColor[4]; // for CGEN_CONST and AGEN_CONST + + unsigned int stateBits; // GLS_xxxx mask + + acff_t adjustColorsForFog; + + EGLFogOverride mGLFogColorOverride; + + surfaceSprite_t *ss; + + // Whether this object emits a glow or not. + bool glow; +} 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 + int lightmapIndex[MAXLIGHTMAPS]; // for a shader to match, both name and lightmapIndex must match + byte styles[MAXLIGHTMAPS]; + + int index; // this shader == tr.shaders[index] + int 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; + + bool 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 + bool explicitlyDefined; // found in a .shader file + bool entityMergable; // merge across entites optimizable (smoke, blood) + + bool isBumpMap; + +#ifdef _XBOX + bool needsNormal; + bool needsTangent; +#endif + + 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 + bool polygonOffset; // set for decals and other items that must be offset + bool 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 (namely skies) + + 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]; +} 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 + +#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 2 +#else +#define NUM_SCRATCH_IMAGES 16 +#endif + +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 int lightmapsNone[MAXLIGHTMAPS]; +extern const int lightmaps2d[MAXLIGHTMAPS]; +extern const int lightmapsVertex[MAXLIGHTMAPS]; +extern const int 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 int *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]; + + 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 ); + +/* +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..9f25ad0 --- /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..fb484a4 --- /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..931315d --- /dev/null +++ b/code/renderer/tr_mesh.cpp @@ -0,0 +1,437 @@ +// 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 + +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 + +================= +*/ +static int R_ComputeLOD( trRefEntity_t *ent ) { + 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 + + // 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 ) { +#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..53ce924 --- /dev/null +++ b/code/renderer/tr_model.cpp @@ -0,0 +1,1197 @@ +// 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 + // + if ( pvDiskBufferIfJustLoaded ) + { + Z_MorphMallocTag( pvDiskBufferIfJustLoaded, eTag ); + } + else + { + 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... + // + bAlreadyCached = qtrue; + assert( mod->md3[lod] == buffer ); +// memcpy( mod->md3[lod], buffer, size ); // and don't do this now, since it's the same thing + + 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..40d349d --- /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..1a82ca3 --- /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..2e98924 --- /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..ab3c557 --- /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..96a096d --- /dev/null +++ b/code/renderer/tr_scene.cpp @@ -0,0 +1,402 @@ +// 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; + + 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..63ae917 --- /dev/null +++ b/code/renderer/tr_shade.cpp @@ -0,0 +1,2776 @@ +// 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 ); + + 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; +#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, 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; + 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 = 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); + 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 + { + 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); + } +} + +#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] ); + } + + // + // 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..6af98d1 --- /dev/null +++ b/code/renderer/tr_shade_calc.cpp @@ -0,0 +1,1587 @@ +// 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 ) { + +} +#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 ) { + +} +#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 ) { + +} +#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..5a7fa1c --- /dev/null +++ b/code/renderer/tr_shader.cpp @@ -0,0 +1,4122 @@ +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" +#include "tr_stl.h" + +const int lightmapsNone[MAXLIGHTMAPS] = +{ + LIGHTMAP_NONE, + LIGHTMAP_NONE, + LIGHTMAP_NONE, + LIGHTMAP_NONE +}; + +const int lightmaps2d[MAXLIGHTMAPS] = +{ + LIGHTMAP_2D, + LIGHTMAP_2D, + LIGHTMAP_2D, + LIGHTMAP_2D +}; + +const int lightmapsVertex[MAXLIGHTMAPS] = +{ + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX, + LIGHTMAP_BY_VERTEX +}; + +const int 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 int *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 int *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, !shader.noPicMip, !shader.noTC, 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, !shader.noPicMip, !shader.noTC, 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, !shader.noPicMip, !shader.noTC, 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, !shader.noPicMip, !shader.noTC, 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; + stage->bundle[0].image = tr.scratchImage[stage->bundle[0].videoMapHandle]; + } + } +//#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, !shader.noPicMip, 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; + } + 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, !shader.noTC, 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(); + + 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 + // + 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; + } + + 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 int *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 int *R_FindLightmap( const int *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 int *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..750a75b --- /dev/null +++ b/code/renderer/tr_shadows.cpp @@ -0,0 +1,809 @@ +// 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) + StencilShadower.RenderEdges(); +#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) + VVdlight_t *dl; + + /*for(int i = 0; i < VVLightMan.num_dlights; i++) + { + if(tess.dlightBits & (1 << i)) + {*/ + dl = &VVLightMan.dlights[0];//i]; + if(StencilShadower.BuildFromLight(dl)) + 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, 0xffffffff ); + qglStencilMask( 0xffffffff ); + 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, (480 - cY), (cX + radX), (480 - (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 ); +} \ 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..785aa37 --- /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..99566a5 --- /dev/null +++ b/code/renderer/tr_stl.cpp @@ -0,0 +1,76 @@ +// 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) +{ + ShaderEntryPtrs.clear(); +} + + +int ShaderEntryPtrs_Size(void) +{ + return ShaderEntryPtrs.size(); +} + + +void ShaderEntryPtrs_Insert(const char *token, const char *p) +{ + 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) +{ + 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..536e2a7 --- /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..5cce024 --- /dev/null +++ b/code/renderer/tr_surface.cpp @@ -0,0 +1,2383 @@ +// tr_surf.c + +// leave this as first line for PCH reasons... +// +#include "../server/exe_headers.h" + + +#include "tr_local.h" + +/* + + 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*/; + + 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]]; + + 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] = (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 +============= +*/ +void RB_SurfaceMesh(md3Surface_t *surface) { + int j; + float backlerp; + int *triangles; + float *texCoords; + int indexes; + int Bob, Doug; + int numVerts; + + 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; + float *normal; + int ndx; + int Bob; + int numPoints; + int dlightBits; +#ifdef _XBOX + unsigned char *indices; + unsigned short *tessIndexes; + unsigned short *v; +#else + unsigned int *indices; + glIndex_t *tessIndexes; + float *v; +#endif + + 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]); + + if(tess.shader->needsTangent || tess.dlightBits) + { + 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) { +#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 + } + } +#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 // _XBOX + + 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 +================== +*/ +static bool RB_TestZFlare( vec3_t point) { + 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 + float depth = 0.0f; + bool visible; + float screenZ; + + // read back the z buffer contents +#ifdef _XBOX + depth = 0.0f; +#else + 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 ); +#endif + + screenZ = backEnd.viewParms.projectionMatrix[14] / + ( ( 2*depth - 1 ) * backEnd.viewParms.projectionMatrix[11] - backEnd.viewParms.projectionMatrix[10] ); + + visible = ( -eye[2] - -screenZ ) < 24; + return visible; +} + +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; + + // 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..482e271 --- /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..2107a6c --- /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..9848190 --- /dev/null +++ b/code/renderer/tr_types.h @@ -0,0 +1,240 @@ +#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 +*/ + +} 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..2bb04db --- /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/renderer/vssver.scc b/code/renderer/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..2e2475ed674e9a96bcb6c430c130e21f31a1f33c GIT binary patch literal 864 zcmW;Ke=L-790%}|b7Fqy>^x78T-@Of7q094I3!HPVj^{7*-lxbHX=o6cYaJpCDE^; zRIH6ltfn7Rm~3iRisok;ySkxSNhj<5JkLMRUeD+AeZHU1_w$w78L@Kv?NpWXPs8s` zx+XgvTYV@P{=RC(VKI+2-hpd(AC=r+=NaLP4SaT_Xl=I-yRqtc+?crEcjpF2d8jp>9E^IbZ6RVjuX5tR8r8sK3vYgGB2j^#~m)p;AnS4js zovrHAMe=!@v7Uefs)kkmIWnFICgEDio?l+0qa*=uh369GcZ{#DkaRu;k8}86;!BH3 z6W9qJOKB~9`}sO)32%ez4Mk_P$+tv=^(i)xckB&LaB5xIt&af*iX~u6gWB8YAuo zM;wZZ&J2_@`@6#q4`$ZdP5TJCeh+veA+02?ewqk@MR0$BM{rZMp0J0-a79F_x-6oC zp!@TLwR_t+y>Zu=^nSW z+3(dw*}x&NRfj>n5aw;7fJ5PO-Ds|@&0*$$1LiE&>9H%l9ybzs^t}G!5I2NB+Kygo dW$vp1mcEWrQ0H@)cp-`!(6JAk;?FYo?>{EP)s_GN literal 0 HcmV?d00001 diff --git a/code/server/exe_headers.cpp b/code/server/exe_headers.cpp new file mode 100644 index 0000000..67f2b31 --- /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..03475b9 --- /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..f1768b1 --- /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..fc80491 --- /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("auto"); + } + + 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..9543660 --- /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..5f961c5 --- /dev/null +++ b/code/server/sv_game.cpp @@ -0,0 +1,715 @@ +// 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 ); + + // 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..7804557 --- /dev/null +++ b/code/server/sv_init.cpp @@ -0,0 +1,596 @@ + +// 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" + +/* +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 int numVehicles; +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(); + + numVehicles = 0; + + if (svs.clients) + { + SV_FreeClient( svs.clients ); + } +} +#endif + +qboolean CM_SameMap(char *server); +qboolean CM_HasTerrain(void); +void Cvar_Defrag(void); + +/* +================ +SV_SpawnServer + +Change the server to a new map, taking all connected +clients along with it. +================ +*/ +void SV_SpawnServer( char *server, ForceReload_e eForceReload, qboolean bAllowScreenDissolve ) +{ + int i; + int checksum; + +// 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; + + // Temporary code to turn on HDR effect for specific maps only + if (!Q_stricmp(server, "t3_rift")) + { + Cvar_Set( "r_hdreffect", "1" ); + } + else + { + Cvar_Set( "r_hdreffect", "0" ); + } +#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); + +#ifdef _XBOX + // disable vsync during load for speed + qglDisable(GL_VSYNC); +#endif + + // 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 + + // 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 + CL_StartHunkUsers(); + CM_LoadMap( va("maps/%s.bsp", server), qfalse, &checksum ); + RE_LoadWorldMap(va("maps/%s.bsp", server)); +#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(); + Z_Validate(); + Z_Validate(); + Z_Validate(); + + 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..391fd77 --- /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..590a912 --- /dev/null +++ b/code/server/sv_savegame.cpp @@ -0,0 +1,2002 @@ +// 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 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" + +#ifdef _XBOX +#include +//support for mbstowcs +HANDLE sg_Handle; +#define SG_BUFFERSIZE 8192 +byte sg_Buffer[SG_BUFFERSIZE]; +int sg_BufferSize; +//used for save game reading +int sg_CurrentBufferPos; + +#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; + + +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 ); + + + +#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 + strlen(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 ) +{ +#ifndef _XBOX + LPCSTR psLocalFilename = SG_AddSavePath( psPathlessBaseName ); + + FS_DeleteUserGenFile( 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_ALWAYS, 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 + + +qboolean gbSGWriteFailed = qfalse; + +static qboolean SG_Create( LPCSTR psPathlessBaseName ) +{ + gbSGWriteFailed = qfalse; + +#ifdef _XBOX + char psLocalFilename[filepathlength]; + char psScreenshotFilename[filepathlength]; + unsigned short widecharstring[filepathlength]; + 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 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 + WriteFile(sg_Handle, // handle to file + &sg_validationHeader, // data buffer + sizeof (sg_validationHeader), // 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; + + // attempt to copy the last screenshot to the save game directory + if( !CopyFile("u:\\saveimage.xbx", psScreenshotFilename, FALSE) ) + { + CopyFile("d:\\base\\media\\defaultsaveimage.xbx", psScreenshotFilename, 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 + +qboolean SG_CloseWrite() +{ + DWORD bytesWritten; + //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; + //get the length of the file + unsigned int filelength =GetFileSize (sg_Handle, NULL); + // create the validation code + sg_validationHeader.dwFileLength = filelength; + // Release signature resources + DWORD 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 + WriteFile (sg_Handle, &sg_validationHeader,sizeof (sg_validationHeader),&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 + +#ifdef _XBOX + 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 , "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 + if (!ReadFile( sg_Handle, &sg_validationHeader, sizeof(sg_validationHeader), &bytesRead, NULL )) + { + SG_Close(); + Com_Printf (S_COLOR_RED "File \"%s\" has no sig"); + return qfalse; + } + //initialize buffer data + sg_BufferSize = 0; + sg_CurrentBufferPos =0; + +#endif + giSaveGameVersion=-1;//jic + SG_Read('_VER', &giSaveGameVersion, sizeof(giSaveGameVersion)); + 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), "auto") ) + { + Com_Printf (S_COLOR_RED "Can't wipe 'auto'\n"); + return; + } + SG_WipeSavegame(Cmd_Argv(1)); +// 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; +void SV_LoadGame_f(void) +{ + 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; + } + +#ifndef _XBOX // VVFIXME : Part of super-bootleg SG hackery + if (!stricmp (psFilename, "current")) + { + Com_Printf (S_COLOR_RED "Can't load from \"current\"\n"); + return; + } +#endif + + // 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 = "auto"; // 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" ); + + char *psMapNameOfAutoSave = SG_GetSaveGameMapName("auto"); + + 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; + if (!SG_ReadSavegame(psFilename)) { + gbAlreadyDoingLoad = qfalse; // do NOT do this here now, need to wait until client spawn, unless the load failed. + } else + { + 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, "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; + + 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 ); + } +} + +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 ); + } + 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; + + 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) +{ + 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" ); +//JLF +#ifdef _XBOX + char mapname[filepathlength]; + char numberedmapname[filepathlength]; + int mapnumber =0; + char numberbuffer[10]; + if ( !strcmp("JKSG3",psPathlessBaseName)) + { + //strcpy(mapname, psMapName); + strcpy (mapname, psPathlessBaseName); + strcpy( numberedmapname, mapname); + } + else + { + strcpy(mapname, psMapName); + strcpy(numberedmapname, psPathlessBaseName); + } + while (!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",psPathlessBaseName)) + { + SG_StoreSaveGameComment(va("--> %s <--",psMapName)); + } + + if(!SG_Create( "current" )) + { + Com_Printf (GetString_FailedToOpenSaveGame("current",qfalse));//S_COLOR_RED "Failed to create savegame\n"); + SG_WipeSavegame( "current" ); + sv_testsave->integer = iPrevTestSave; + 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, sMapCmd); +// SG_WriteScreenshot(qbAutosave, sMapCmd); + SG_Append('MPCM', sMapCmd, sizeof(sMapCmd)); + 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("current",qfalse));//S_COLOR_RED "Failed to write savegame!\n"); + SG_WipeSavegame( "current" ); + sv_testsave->integer = iPrevTestSave; + return qfalse; + } + + SG_Move( "current", psPathlessBaseName ); + + + sv_testsave->integer = iPrevTestSave; + return qtrue; +} + +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_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) + { + 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)); + 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 + + if(!SG_Close()) + { + 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; +} + + +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; + } +} + +// 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)... + // + pbOut = CompressMem_AllocScratchBuffer(iLength*2); + // + // compress it... + // + int iOutputLength = Compress_RLE(pbData, iLength, pbOut); + // + // worth compressing?... + // + if (iOutputLength >= iLength) + return -1; + // + // compression code works? (I'd hope this is always the case, but for safety)... + // + if (!Verify_RLE(pbData, pbOut, iLength)) + return -1; + + return iOutputLength; +} + + +#ifdef _XBOX// function for xbox +/* +int SG_Write(const void * chid, const int bytesize, fileHandle_t fhSG) +{ + DWORD bytesWritten; + int currentsize; + + + 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; + } + + return bytesWritten; +} +*/ + + +int SG_Write(const void * chid, const int bytesize, fileHandle_t fhSG) +{ + DWORD bytesWritten; + int currentsize; + + if (sg_BufferSize + bytesize>= SG_BUFFERSIZE) + { + 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 bytesWritten; +// else + { + 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); + + uiSaved = SG_Write(&chid, sizeof(chid), fhSaveGame); + + byte *pbCompressedData = NULL; + int 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... + // + uiSaved += SG_Write(&iLength, sizeof(iLength), fhSaveGame); + // + // uncompressed data... + // + uiSaved += SG_Write( pvData, iLength, fhSaveGame); + // + // CRC... + // + 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; +} +*/ + +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 (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 + )) + { + sg_BufferSize = bytesRead; + sg_CurrentBufferPos = 0; + bufferptr = sg_Buffer; + //sig processing + } + 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, 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) + { + 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) + { + 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... + // + DeCompress_RLE((byte *)pvAddress, pTempRLEData, iLength); + // + // 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) + { + 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) + { + 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); + } +} + +////////////////// eof //////////////////// + diff --git a/code/server/sv_snapshot.cpp b/code/server/sv_snapshot.cpp new file mode 100644 index 0000000..b627362 --- /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..706025c --- /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/server/vssver.scc b/code/server/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..f09e1ce3aa1091537ba4d13b4b35786a2c4a9661 GIT binary patch literal 208 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiY8J-+x?G#djL!~p3#o>Nlv#Sh9e0!3nhe2&f4 zt%r|Y2J_>9d{&1)Uc9>Z)Ui@qqd3hi| c707>GeaGUeMl{&GG$7whN&dOGc{26=Y&N8CY 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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..925f52d --- /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..90bce42 --- /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..dfc8435 --- /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/smartheap/vssver.scc b/code/smartheap/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..f8d6308ee75c26727947dc7aa312b59f9ea62565 GIT binary patch literal 112 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiW+z0={%Vq*Y|1h-K#?3Ezn5X@ u)X#t3g88{X{>FJp^3kck!TdZRe+BE6vln-DgZcSDetp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/code/starwars.vcproj.vspscc b/code/starwars.vcproj.vspscc new file mode 100644 index 0000000..794f014 --- /dev/null +++ b/code/starwars.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/tonet.bat b/code/tonet.bat new file mode 100644 index 0000000..1add0c0 --- /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..bf55e62 --- /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..a89342a --- /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..cd35d2a --- /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..292c0d7 --- /dev/null +++ b/code/ui/menudef.h @@ -0,0 +1,138 @@ + +#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 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..b82b7f8 --- /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..5a5d508 --- /dev/null +++ b/code/ui/ui_atoms.cpp @@ -0,0 +1,480 @@ +/********************************************************************** + 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 + +================= +*/ +void UI_SetActiveMenu( const char* menuname,const char *menuID ) +{ + // 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" ); + UI_InGameMenu(menuID); + return; + } + + if ( Q_stricmp (menuname, "datapad") == 0 ) + { + ui.Cvar_Set( "cl_paused", "1" ); + UI_DataPadMenu(); + return; + } + + if ( Q_stricmp (menuname, "missionfailed_menu") == 0 ) + { + Menus_CloseAll(); + Menus_ActivateByName("missionfailed_menu"); + ui.Key_SetCatcher( KEYCATCH_UI ); + 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..3485d15 --- /dev/null +++ b/code/ui/ui_connect.cpp @@ -0,0 +1,95 @@ +// 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 ) { +#if 0 + // if connecting to a local host, don't draw anything before the + // gamestate message. This allows cinematics to start seamlessly + if ( connState < CA_LOADING && !strcmp( cls.servername, "localhost" ) ) { + UI_SetColor( g_color_table[0] ); + re.DrawFill (0, 0, re.scrWidth, re.scrHeight); + UI_SetColor( NULL ); + return; + } +#endif + +// qboolean qValid; +// byte *levelPic = ui.SCR_GetScreenshot(&qValid); + // draw the dialog background +// if (!qValid) + { + UI_DrawHandlePic(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, uis.menuBackShader ); + } +// else { +// UI_DrawThumbNail(0,SCREEN_HEIGHT, SCREEN_WIDTH, -SCREEN_HEIGHT, levelPic ); +// } +} + + +/* +======================== +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..5593389 --- /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 ); + +#endif diff --git a/code/ui/ui_main.cpp b/code/ui/ui_main.cpp new file mode 100644 index 0000000..9080a7b --- /dev/null +++ b/code/ui/ui_main.cpp @@ -0,0 +1,6314 @@ +// 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 + +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 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; + +#ifdef _XBOX +#define MAX_SAVELOADFILES 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; + char currentSaveFileMap[MAX_TOKEN_CHARS]; // map save game is from +} savedata_t; + +static savedata_t s_savedata[MAX_SAVELOADFILES]; +void UI_SetActiveMenu( const char* menuname,const char *menuID ); +void ReadSaveDirectory (void); +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_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_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 ); + + +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 ); + +void Menus_CloseByName(const char *p); + +// 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 + + +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 +}; + +#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 +} + +/* +=============== +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].currentSaveFileComments; + } + else + { + return s_savedata[index].currentSaveFileDateTimeString; + } + } + 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) + { + 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; + } + } + } + + 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; +} + + +/* +================= +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 + + } + } + else if (Q_stricmp(name, "savegame") == 0) + { + 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); // Get a name to save to +#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 + } + 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_APPEND, "dpforcenext\n"); + } + else if (Q_stricmp(name, "prevDataPadForcePower") == 0) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "dpforceprev\n"); + } + else if (Q_stricmp(name, "nextDataPadWeapon") == 0) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "dpweapnext\n"); + } + else if (Q_stricmp(name, "prevDataPadWeapon") == 0) + { + ui.Cmd_ExecuteText( EXEC_APPEND, "dpweapprev\n"); + } + 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, "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, "weaponhelpactive") == 0) + { + UI_WeaponHelpActive(); + } + // initialize weapon selection screen + else if (Q_stricmp(name, "initweaponselect") == 0) + { + UI_InitWeaponSelect(); + } + 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) + { + 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, "decrementforcepowerlevel") == 0) + { + UI_DecrementForcePowerLevel(); + } + else if (Q_stricmp(name, "getmousepitch") == 0) + { + Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1"); + } + 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 ); + Sys_Reboot("multiplayer"); + } +#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 + +#ifdef _XBOX + void 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); + + XCreateSaveGame("U:\\", saveGameName, OPEN_ALWAYS, 0,directoryInfo, filepathlength); + + strcpy (psLocalFilename , directoryInfo); + strcat (psLocalFilename , "saveimage.xbx"); + + + R_UpdateSaveGameImage(psLocalFilename); +#else +/* if (!ui.SG_GetSaveImage(s_savedata[s_savegame.currentLine].currentSaveFileName, &screenShotBuf)) +>>>>>>> 1.30 + { + memset( screenShotBuf,0,(SG_SCR_WIDTH * SG_SCR_HEIGHT * 4)); + } +*/ +#endif +} + +/* +================= +UI_FeederCount +================= +*/ +static int UI_FeederCount(float feederID) +{ +#ifdef _XBOX +//JLF MPNOTNEEDED + static bool firstSaveRequest = 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; + } + // 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_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; + 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 ) +{ + // Get the list of possible languages + uiInfo.languageCount = SE_GetNumLanguages(); // this does a dir scan, so use carefully + + 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("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1"); + + 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,*holdBuffer,*token2; + 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 if (Q_stricmp(name, "ui_mousePitch") == 0) + { + if (val == 0) + { + Cvar_SetValue( "m_pitch", 0.022f ); + } + else + { + Cvar_SetValue( "m_pitch", -0.022f ); + } + } + 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( "menu/new/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_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_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_HUD); + ui.Draw_DataPad(DP_OBJECTIVES); + break; + + case UI_DATAPAD_WEAPONS: + ui.Draw_DataPad(DP_HUD); + 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_HUD); + 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 (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 +================= +*/ +void UI_InGameMenu(const char*menuID) +{ +#ifdef _XBOX + ui.PrecacheScreenshot(); +#endif + + Menus_CloseByName("mainhud"); + + if (menuID) + { + Menus_ActivateByName(menuID); + } + else + { + 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; + + 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",pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum]); + 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 + 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_INACTIVE; + } + else + { + item->window.flags |= WINDOW_INACTIVE; + } + } +} + +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; + } + + // 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; + } + + DC->startLocalSound(uiInfo.uiDC.Assets.forceUnchosenSound, CHAN_AUTO ); + + 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 ); + + 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); + +// 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; + } + + // 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 (pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum]>2) + { // Too big, can't be incremented + return; + } + + // Increment power level. + DC->startLocalSound(uiInfo.uiDC.Assets.forceChosenSound, CHAN_AUTO ); + + uiInfo.forcePowerUpdated = forcePowerI; // Remember which power was updated + + pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum]++; // Increment it + pState->forcePowersKnown |= ( 1 << powerEnums[forcePowerI].powerEnum ); + + UI_SetHexPicLevel( menu,uiInfo.forcePowerUpdated,pState->forcePowerLevel[powerEnums[forcePowerI].powerEnum],qtrue ); + + 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); +} + + +// 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"); + + if (item) + { + // Make it active + if (activeFlag) + { + item->window.flags &= ~WINDOW_INACTIVE; + } + else + { + item->window.flags |= WINDOW_INACTIVE; + } + } +} + +// 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 ) +{ + UI_WeaponAllocBeginButton(qfalse); + uiInfo.selectedWeapon1 = NOWEAPON; + uiInfo.selectedWeapon2 = NOWEAPON; + uiInfo.selectedThrowWeapon = NOWEAPON; +} + +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"); + } + +} + +// 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 ) +{ + itemDef_s *item, *iconItem,*litIconItem; + menuDef_t *menu; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + iconItem = (itemDef_s *) Menu_FindItemByName(menu, iconItemName ); + litIconItem = (itemDef_s *) Menu_FindItemByName(menu, litIconItemName ); + + char *chosenItemName, *chosenButtonName; + + // has this weapon already been chosen? + if (weaponIndex == uiInfo.selectedWeapon1) + { + UI_RemoveWeaponSelection ( 1 ); + return; + } + else if (weaponIndex == uiInfo.selectedWeapon2) + { + UI_RemoveWeaponSelection ( 2 ); + return; + } + + // See if either slot is empty + if ( uiInfo.selectedWeapon1 == NOWEAPON ) + { + chosenItemName = "chosenweapon1_icon"; + chosenButtonName = "chosenweapon1_button"; + uiInfo.selectedWeapon1 = weaponIndex; + uiInfo.selectedWeapon1AmmoIndex = ammoIndex; + + memcpy( uiInfo.selectedWeapon1ItemName,hexBackground,sizeof(uiInfo.selectedWeapon1ItemName)); + + //Save the lit and unlit icons for the selected weapon slot + uiInfo.litWeapon1Icon = litIconItem->window.background; + uiInfo.unlitWeapon1Icon = iconItem->window.background; + + uiInfo.weapon1ItemButton = uiInfo.runScriptItem; + uiInfo.weapon1ItemButton->descText = "@MENUS_CLICKREMOVE"; + } + else if ( uiInfo.selectedWeapon2 == NOWEAPON ) + { + chosenItemName = "chosenweapon2_icon"; + chosenButtonName = "chosenweapon2_button"; + uiInfo.selectedWeapon2 = weaponIndex; + uiInfo.selectedWeapon2AmmoIndex = ammoIndex; + + memcpy( uiInfo.selectedWeapon2ItemName,hexBackground,sizeof(uiInfo.selectedWeapon2ItemName)); + + //Save the lit and unlit icons for the selected weapon slot + uiInfo.litWeapon2Icon = litIconItem->window.background; + uiInfo.unlitWeapon2Icon = iconItem->window.background; + + uiInfo.weapon2ItemButton = uiInfo.runScriptItem; + uiInfo.weapon2ItemButton->descText = "@MENUS_CLICKREMOVE"; + } + else // Both slots are used, can't add it. + { + return; + } + + item = (itemDef_s *) Menu_FindItemByName(menu, chosenItemName ); + if ((item) && (iconItem)) + { + item->window.background = iconItem->window.background; + item->window.flags |= WINDOW_VISIBLE; + } + + // Turn on chosenweapon button so player can unchoose the weapon + item = (itemDef_s *) Menu_FindItemByName(menu, chosenButtonName ); + if (item) + { + item->window.background = iconItem->window.background; + item->window.flags |= WINDOW_VISIBLE; + } + + // Switch hex background to be 'on' + item = (itemDef_s *) Menu_FindItemByName(menu, hexBackground ); + if (item) + { + item->window.foreColor[0] = 0; + item->window.foreColor[1] = 1; + item->window.foreColor[2] = 0; + item->window.foreColor[3] = 1; + + } + + // 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; + } + } + + if( soundfile ) + { + DC->startLocalSound(DC->registerSound(soundfile, qfalse), CHAN_LOCAL ); + } + + 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 ) +{ + itemDef_s *item; + menuDef_t *menu; + char *chosenItemName, *chosenButtonName,*background; + int ammoIndex,weaponIndex; + + menu = Menu_GetFocused(); // Get current menu + + // Which item has it? + if ( weaponSelectionIndex == 1 ) + { + chosenItemName = "chosenweapon1_icon"; + chosenButtonName = "chosenweapon1_button"; + background = uiInfo.selectedWeapon1ItemName; + ammoIndex = uiInfo.selectedWeapon1AmmoIndex; + weaponIndex = uiInfo.selectedWeapon1; + + if (uiInfo.weapon1ItemButton) + { + uiInfo.weapon1ItemButton->descText = "@MENUS_CLICKSELECT"; + uiInfo.weapon1ItemButton = NULL; + } + } + else if ( weaponSelectionIndex == 2 ) + { + chosenItemName = "chosenweapon2_icon"; + chosenButtonName = "chosenweapon2_button"; + background = uiInfo.selectedWeapon2ItemName; + ammoIndex = uiInfo.selectedWeapon2AmmoIndex; + weaponIndex = uiInfo.selectedWeapon2; + + if (uiInfo.weapon2ItemButton) + { + uiInfo.weapon2ItemButton->descText = "@MENUS_CLICKSELECT"; + uiInfo.weapon2ItemButton = NULL; + } + } + else + { + return; + } + + // Reset background of upper icon + item = (itemDef_s *) Menu_FindItemByName( menu, background ); + if ( item ) + { + item->window.foreColor[0] = 0.0f; + item->window.foreColor[1] = 0.5f; + item->window.foreColor[2] = 0.0f; + item->window.foreColor[3] = 1.0f; + } + + // Hide it icon + item = (itemDef_s *) Menu_FindItemByName( menu, chosenItemName ); + if ( item ) + { + item->window.flags &= ~WINDOW_VISIBLE; + } + + // Hide button + item = (itemDef_s *) Menu_FindItemByName( menu, chosenButtonName ); + if ( item ) + { + item->window.flags &= ~WINDOW_VISIBLE; + } + + // Get player state + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + // Remove weapon + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + if ((weaponIndex>0) && (weaponIndexstats[ STAT_WEAPONS ] &= ~( 1 << weaponIndex ); + } + + // Remove ammo too + if ((ammoIndex>0) && (ammoIndexammo[ ammoIndex ] = 0; + } + } + } + + // Now do a little clean up + if ( weaponSelectionIndex == 1 ) + { + uiInfo.selectedWeapon1 = NOWEAPON; + memset(uiInfo.selectedWeapon1ItemName,0,sizeof(uiInfo.selectedWeapon1ItemName)); + uiInfo.selectedWeapon1AmmoIndex = 0; + } + else if ( weaponSelectionIndex == 2 ) + { + uiInfo.selectedWeapon2 = NOWEAPON; + memset(uiInfo.selectedWeapon2ItemName,0,sizeof(uiInfo.selectedWeapon2ItemName)); + uiInfo.selectedWeapon2AmmoIndex = 0; + } + + DC->startLocalSound(DC->registerSound("sound/interface/weapon_deselect.mp3", qfalse), CHAN_LOCAL ); + + UI_WeaponsSelectionsComplete(); // Test to see if the mission begin button should turn on or off + + +} + +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 ) +{ + itemDef_s *item, *iconItem,*litIconItem; + menuDef_t *menu; + + menu = Menu_GetFocused(); // Get current menu + + if (!menu) + { + return; + } + + iconItem = (itemDef_s *) Menu_FindItemByName(menu, iconItemName ); + litIconItem = (itemDef_s *) Menu_FindItemByName(menu, litIconItemName ); + + char *chosenItemName, *chosenButtonName; + + // Has a throw weapon already been chosen? + if (uiInfo.selectedThrowWeapon!=NOWEAPON) + { + // Clicked on the selected throwable weapon + if (uiInfo.selectedThrowWeapon==weaponIndex) + { // Deselect it + UI_RemoveThrowWeaponSelection(); + } + return; + } + + chosenItemName = "chosenthrowweapon_icon"; + chosenButtonName = "chosenthrowweapon_button"; + uiInfo.selectedThrowWeapon = weaponIndex; + uiInfo.selectedThrowWeaponAmmoIndex = ammoIndex; + uiInfo.weaponThrowButton = uiInfo.runScriptItem; + + if (uiInfo.weaponThrowButton) + { + uiInfo.weaponThrowButton->descText = "@MENUS_CLICKREMOVE"; + } + + memcpy( uiInfo.selectedThrowWeaponItemName,hexBackground,sizeof(uiInfo.selectedWeapon1ItemName)); + + //Save the lit and unlit icons for the selected weapon slot + uiInfo.litThrowableIcon = litIconItem->window.background; + uiInfo.unlitThrowableIcon = iconItem->window.background; + + item = (itemDef_s *) Menu_FindItemByName(menu, chosenItemName ); + if ((item) && (iconItem)) + { + item->window.background = iconItem->window.background; + item->window.flags |= WINDOW_VISIBLE; + } + + // Turn on throwchosenweapon button so player can unchoose the weapon + item = (itemDef_s *) Menu_FindItemByName(menu, chosenButtonName ); + if (item) + { + item->window.background = iconItem->window.background; + item->window.flags |= WINDOW_VISIBLE; + } + + // Switch hex background to be 'on' + item = (itemDef_s *) Menu_FindItemByName(menu, hexBackground ); + if (item) + { + item->window.foreColor[0] = 0.0f; + item->window.foreColor[1] = 0.0f; + item->window.foreColor[2] = 1.0f; + item->window.foreColor[3] = 1.0f; + + } + + // 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; + } + } + + if( soundfile ) + { + DC->startLocalSound(DC->registerSound(soundfile, qfalse), CHAN_LOCAL ); + } + + 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 ) +{ + itemDef_s *item; + menuDef_t *menu; + char *chosenItemName, *chosenButtonName,*background; + + menu = Menu_GetFocused(); // Get current menu + + // Weapon not chosen + if ( uiInfo.selectedThrowWeapon == NOWEAPON ) + { + return; + } + + chosenItemName = "chosenthrowweapon_icon"; + chosenButtonName = "chosenthrowweapon_button"; + background = uiInfo.selectedThrowWeaponItemName; + + // Reset background of upper icon + item = (itemDef_s *) Menu_FindItemByName( menu, background ); + if ( item ) + { + item->window.foreColor[0] = 0.0f; + item->window.foreColor[1] = 0.0f; + item->window.foreColor[2] = 0.5f; + item->window.foreColor[3] = 1.0f; + } + + // Hide it icon + item = (itemDef_s *) Menu_FindItemByName( menu, chosenItemName ); + if ( item ) + { + item->window.flags &= ~WINDOW_VISIBLE; + } + + // Hide button + item = (itemDef_s *) Menu_FindItemByName( menu, chosenButtonName ); + if ( item ) + { + item->window.flags &= ~WINDOW_VISIBLE; + } + + // Get player state + + client_t* cl = &svs.clients[0]; // 0 because only ever us as a player + + if (!cl) // No client, get out + { + return; + } + + // Remove weapon + if (cl->gentity && cl->gentity->client) + { + playerState_t* pState = cl->gentity->client; + + if ((uiInfo.selectedThrowWeapon>0) && (uiInfo.selectedThrowWeaponstats[ STAT_WEAPONS ] &= ~( 1 << uiInfo.selectedThrowWeapon ); + } + + // Remove ammo too + if ((uiInfo.selectedThrowWeaponAmmoIndex>0) && (uiInfo.selectedThrowWeaponAmmoIndexammo[ uiInfo.selectedThrowWeaponAmmoIndex ] = 0; + } + + } + + // Now do a little clean up + uiInfo.selectedThrowWeapon = NOWEAPON; + memset(uiInfo.selectedThrowWeaponItemName,0,sizeof(uiInfo.selectedThrowWeaponItemName)); + uiInfo.selectedThrowWeaponAmmoIndex = 0; + + if (uiInfo.weaponThrowButton) + { + uiInfo.weaponThrowButton->descText = "@MENUS_CLICKSELECT"; + uiInfo.weaponThrowButton = NULL; + } + + DC->startLocalSound(DC->registerSound("sound/interface/weapon_deselect.mp3", qfalse), CHAN_LOCAL ); + + UI_WeaponsSelectionsComplete(); // Test to see if the mission begin button should turn on or off + +} + +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; + } + } + +} + +/* +================= +ReadSaveDirectory +================= +*/ +//JLFSAVEGAME MPNOTUSED +#ifdef _XBOX //xbox version +//for the xbox reading the save directory will consist of +//iterating through the save game folders + +void ReadSaveDirectory (void) +{ + int i; + char *holdChar; + int len; + int fileCnt; + + // 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; + + // 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 + char saveGameName[filepathlength]; + + wcstombs(saveGameName, SaveGameData.szSaveGameName, filepathlength); + strcpy( holdChar, saveGameName); + + + if ( Q_stricmp("current",saveGameName)!=0 ) + { + time_t result; + if (Q_stricmp("auto",saveGameName)==0) + { + Cvar_Set("ui_ResumeOK", "1" ); + } + else + { // Is this a valid file??? & Get comment of file + //create full path name + + result = ui.SG_GetSaveGameComment(saveGameName, s_savedata[s_savegame.saveFileCnt].currentSaveFileComments, s_savedata[s_savegame.saveFileCnt].currentSaveFileMap); + if (result != 0) // ignore Bad save game + { + strcpy(s_savedata[s_savegame.saveFileCnt].currentSaveFileComments,s_savedata[s_savegame.saveFileCnt].currentSaveFileMap); + s_savedata[s_savegame.saveFileCnt].currentSaveFileName = holdChar; + s_savedata[s_savegame.saveFileCnt].currentSaveFileDateTime = result; + holdChar += strlen(holdChar)+1; + + struct tm *localTime; + localTime = localtime( &result ); + strcpy(s_savedata[s_savegame.saveFileCnt].currentSaveFileDateTimeString,asctime( localTime ) ); + s_savegame.saveFileCnt++; + if (s_savegame.saveFileCnt == MAX_SAVELOADFILES) + { + break; + } + } + } + } + + retval =XFindNextSaveGame( searchhandle, &SaveGameData ); + }while(retval); + +} + +#else //pc version + +void ReadSaveDirectory (void) +{ + int i; + char *holdChar; + int len; + int fileCnt; + // 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; + for ( i = 0; i < fileCnt; i++ ) + { + // strip extension + len = strlen( holdChar ); + holdChar[len-4] = '\0'; + + if ( Q_stricmp("current",holdChar)!=0 ) + { + time_t result; + if (Q_stricmp("auto",holdChar)==0) + { + Cvar_Set("ui_ResumeOK", "1" ); + } + else + { // Is this a valid file??? & Get comment of file + result = ui.SG_GetSaveGameComment(holdChar, s_savedata[s_savegame.saveFileCnt].currentSaveFileComments, s_savedata[s_savegame.saveFileCnt].currentSaveFileMap); + if (result != 0) // ignore Bad save game + { + s_savedata[s_savegame.saveFileCnt].currentSaveFileName = holdChar; + s_savedata[s_savegame.saveFileCnt].currentSaveFileDateTime = result; + + struct tm *localTime; + localTime = localtime( &result ); + strcpy(s_savedata[s_savegame.saveFileCnt].currentSaveFileDateTimeString,asctime( localTime ) ); + s_savegame.saveFileCnt++; + if (s_savegame.saveFileCnt == MAX_SAVELOADFILES) + { + break; + } + } + } + } + + holdChar += len + 1; //move to next item + } + + qsort( s_savedata, s_savegame.saveFileCnt, sizeof(savedata_t), UI_SortSaveGames ); + +} + +#endif diff --git a/code/ui/ui_public.h b/code/ui/ui_public.h new file mode 100644 index 0000000..8486f22 --- /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..a2f5768 --- /dev/null +++ b/code/ui/ui_saber.cpp @@ -0,0 +1,861 @@ +// +/* +======================================================================= + +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; + } + + 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..8f70cd1 --- /dev/null +++ b/code/ui/ui_shared.cpp @@ -0,0 +1,11977 @@ +// +// 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 +//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_DemoFrame(); +void G_DemoKeypress(); + +void PlayDemo(); +void UpdateDemoTimer(); +bool TestDemoTimer(); + +//END DEMOCODE +//JLF used by sliders MPMOVED +#define TICK_COUNT 20 + +//JLF MORE PROTOTYPES MPMOVED +qboolean Item_SetFocus(itemDef_t *item, float x, float y); + +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 ); + +//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; +} + + +/* +================= +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) + { + 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, }, + {"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_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_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_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)) + { + 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)) + { + focusItem = (itemDef_s *) Menu_FindItemByName((menuDef_t *) item->parent, name); + 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_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 ); + + 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 + } + 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) +{ + DC->startLocalSound(uiInfo.uiDC.Assets.nullSound, CHAN_VOICE ); + + 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 + {"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} +}; + +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; +} + +/* +=============== +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_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) + { + 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 + {"scrollhidden", ItemParse_scrollhidden, }, +//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 + + + + // 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; + + + 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) + { + Menus_OpenByName("ingameForceHelp"); + } + } + + // First time, show weapons select instructions + if (!Q_stricmp( p, "ingameWpnSelect" ) ) + { + int tier_storyinfo = Cvar_VariableIntegerValue( "tier_storyinfo" ); + + if (tier_storyinfo==1) + { + Menus_OpenByName("ingameWpnSelectHelp"); + } + } + + // 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 + DC->setCVar("ui_hideAcallout" ,"0"); + DC->setCVar("ui_hideBcallout" ,"0"); + DC->setCVar("ui_hideCcallout" ,"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; + } +#ifdef _XBOX +//JLFCALLOUT + //for all the call out items define their functionality +// for (int i =0 ; i < MAX_CALLOUTITEMS; i++) +// { + // menu->calloutitems[i] = (struct itemDef_s *) UI_Alloc(sizeof(itemDef_t)); + // Item_Init(menu->calloutitems[i]); +// } + // fill in the fields + // dpad 1 (up/down) + //menu->calloutitems[0]->window.flags + + +#endif + + + 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(); + char *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)); +} + + +/* +================= +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++) + { + 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) + { + 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 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,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); + } +*/ 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, thumb; + 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; + + // Draw scroll bar if text goes beyond bottom + if (( scrollPtr->iLineCount * scrollPtr->lineHeight ) > size) + { + // 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; + + scrollPtr->endPos = scrollPtr->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_TextScroll_ThumbDrawPosition(item); + if (thumb > y - SCROLLBAR_SIZE - 1) + { + thumb = y - SCROLLBAR_SIZE - 1; + } + DC->drawHandlePic(x, thumb, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb); + } + + // 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 +================= +*/ + +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; + + // 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); + +//JLFLISTBOX MPMOVED +#ifdef _XBOX + listPtr->startPos = 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 + { +//JLF MPMOVED + numlines = item->window.rect.h / listPtr->elementHeight; +//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); + } +//JLF end + // adjust size for item painting + size = item->window.rect.h - 2; + 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) + { + 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 + i = listPtr->startPos - (numlines/2); +#else + i = listPtr->startPos; +#endif + for (; i < count; i++) + { + const char *text; + // always draw at least one + // which may overdraw the box if it is too small for the element + + 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; + menuDef_t *parent = (menuDef_t*)item->parent; + + // Use focus color is it has focus. + if (i == item->cursorPos) + { + color = &parent->focusColor; + } + else + { + color = &item->window.foreColor; + } + + + int textyOffset; + +//JLF MPMOVED +#ifdef _XBOX + float fScaleA = item->textscale; + textyOffset = DC->textHeight (text, fScaleA, item->font); + textyOffset *= -1; + textyOffset /=2; + textyOffset += listPtr->elementHeight/2; +#else + textyOffset = 0; +#endif + + + DC->drawText(x + 4 + listPtr->columnInfo[j].pos, 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); + 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, item->textscale, item->window.foreColor, text, 0, item->textStyle, item->font); + } +//JLF MPMOVED +#ifdef _XBOX + } +#endif + } + + // The chosen text + 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); + } + + 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 + { + VectorSet( angles, 0, curYaw, 0 ); + } + + + 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 ; + ui.R_AddLightToScene(refdef.vieworg, 500, 1, 1, 1); //fixme: specify in menu file! + + DC->addRefEntityToScene( &ent ); + DC->renderScene( &refdef ); + +} + +/* +================= +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) + { + 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, lowLight; + float value; + menuDef_t *parent = (menuDef_t*)item->parent; + + value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + + 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)); + } + + 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, lowLight; + const char *text = ""; + menuDef_t *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)); + } + + text = Item_Multi_Setting(item); + if (*text == '@') // string reference + { + text = SE_GetString( &text[1] ); + } + + + if (item->text) + { + Item_Text_Paint(item); +//JLF +#ifdef _XBOX + if ( item->xoffset) + DC->drawText(item->textRect.x + item->textRect.w + item->xoffset, item->textRect.y, item->textscale, newColor, text, 0, item->textStyle, item->font); + else +#endif + DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, text, 0, item->textStyle, item->font); + } + else + { +//JLF added xoffset + DC->drawText(item->textRect.x +item->xoffset, item->textRect.y, item->textscale, newColor, text, 0, item->textStyle, item->font); + } +} + +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, x; + editFieldDef_t *editDef = (editFieldDef_t *) item->typeData; + + if (item->text) + { + x = item->textRect.x + item->textRect.w + 8; + } + else + { + x = item->window.rect.x; + } + + if (editDef == NULL && item->cvar) + { + return x; + } + + 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; + value /= range; + //value /= (editDef->maxVal - editDef->minVal); + value *= SLIDER_WIDTH; + x += value; + // vm fuckage + //x = x + (((float)value / editDef->maxVal) * SLIDER_WIDTH); + return x; +} + +/* +================= +Item_Slider_Paint +================= +*/ +void Item_Slider_Paint(itemDef_t *item) +{ + vec4_t newColor, lowLight; + float x, y, value; + menuDef_t *parent = (menuDef_t*)item->parent; + + value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; + + 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)); + } + + y = item->window.rect.y; + if (item->text) + { + Item_Text_Paint(item); + x = item->textRect.x + item->textRect.w + 8; + } + else + { + x = item->window.rect.x; + } + DC->setColor(newColor); + DC->drawHandlePic( x, y+2, SLIDER_WIDTH, SLIDER_HEIGHT, DC->Assets.sliderBar ); + + x = Item_Slider_ThumbPosition(item); +// DC->drawHandlePic( x - (SLIDER_THUMB_WIDTH / 2), y - 2, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT, DC->Assets.sliderThumb ); + DC->drawHandlePic( x - (SLIDER_THUMB_WIDTH / 2), y+2, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT, DC->Assets.sliderThumb ); + +} + +/* +================= +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); + } + DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, 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 + { + DC->drawRect(w->rect.x, w->rect.y, w->rect.w, 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); + + if (item->type == ITEM_TYPE_TEXT) + { + rectDef_t r; + r = item->textRect; + //r.y -= r.h; +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&r, x, y)) +#endif + { + item->window.flags |= WINDOW_HASFOCUS; + if (item->focusSound) + { + sfx = &item->focusSound; + } + playSound = qtrue; +#ifdef _IMMERSION + if (item->focusForce) + { + ff = &item->focusForce; + } + playForce = qtrue; +#endif // _IMMERSION + } +#ifndef _XBOX + else + + { + if (oldFocus) + { + oldFocus->window.flags |= WINDOW_HASFOCUS; + if (oldFocus->onFocus) + { + Item_RunScript(oldFocus, oldFocus->onFocus); + } + } + } +#endif + } + else + { + item->window.flags |= WINDOW_HASFOCUS; + if (item->onFocus) + { + Item_RunScript(item, item->onFocus); + } + if (item->focusSound) + { + sfx = &item->focusSound; + } + playSound = qtrue; +#ifdef _IMMERSION + if (item->focusForce) + { + ff = &item->focusForce; + } + playForce = qtrue; +#endif // _IMMERSION + } + + if (playSound && sfx) + { + DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND ); + } + +#ifdef _IMMERSION + if (playForce && ff) + { + DC->startForce( *ff ); + } +#endif // _IMMERSION + 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; + } + } +} + +/* +================= +Menu_HandleMouseMove +================= +*/ +void Menu_HandleMouseMove(menuDef_t *menu, float x, float y) +{ + + //JLFMOUSE I THINK THIS JUST SETS THE FOCUS BASED ON THE MOUSE +#ifdef _XBOX + return ; +#endif + //END JLF + int i, pass; + qboolean focusSet = qfalse; + + itemDef_t *overItem; + if (menu == NULL) + { + return; + } + + if (!(menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) + { + return; + } + + if (itemCapture) + { + //Item_MouseMove(itemCapture, x, y); + return; + } + + if (g_waitingForKey || g_editingField) + { + return; + } + + // FIXME: this is the whole issue of focus vs. mouse over.. + // need a better overall solution as i don't like going through everything twice + for (pass = 0; pass < 2; pass++) + { + 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_INACTIVE) + { + continue; + } + + // items can be enabled and disabled based on cvars + 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 (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) + { + if (pass == 1) + { + overItem = menu->items[i]; + if (overItem->type == ITEM_TYPE_TEXT && overItem->text) + { + if (!Rect_ContainsPoint(&overItem->window.rect, x, y)) + { + continue; + } + } + + // if we are over an item + if (IsVisible(overItem->window.flags)) + { + // different one + Item_MouseEnter(overItem, x, y); + // Item_SetMouseOver(overItem, qtrue); + + // if item is not a decoration see if it can take focus + if (!focusSet) + { + focusSet = Item_SetFocus(overItem, x, y); + } + } + } + + } else if (menu->items[i]->window.flags & WINDOW_MOUSEOVER) + { + Item_MouseLeave(menu->items[i]); + Item_SetMouseOver(menu->items[i], qfalse); + } + } + } +} + +/* +================= +Display_MouseMove +================= +*/ +qboolean Display_MouseMove(void *p, int x, int y) +{ + //JLFMOUSE AGAIN I THINK THIS SHOULD BE MOOT +#ifdef _XBOX + return qtrue; +#endif + //END JLF + 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]); + } +} + +/* +================= +Menus_HandleOOBClick +================= +*/ +void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) +{ + if (menu) + { +//JLFMOUSE +#ifdef _XBOX + Menu_HandleMouseMove(menu, DC->cursorx, DC->cursory); + Menu_HandleKey(menu, key, down); + return; +#endif + int i; + // basically the behaviour we are looking for is if there are windows in the stack.. see if + // the cursor is within any of them.. if not close them otherwise activate them and pass the + // key on.. force a mouse move to activate focus and script stuff + if (down && menu->window.flags & WINDOW_OOB_CLICK) + { + Menu_RunCloseScript(menu); + menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); + } + + for (i = 0; i < menuCount; i++) + { + if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) + { + Menu_RunCloseScript(menu); + menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); + Menus_Activate(&Menus[i]); + Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory); + Menu_HandleKey(&Menus[i], key, down); +#ifdef _XBOX + break; +#endif + } + } + + if (Display_VisibleMenuCount() == 0) + { + if (DC->Pause) + { + DC->Pause(qfalse); + } + } + Display_CloseCinematics(); + } +} + +/* +================= +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, viewmax; +//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 = (item->window.rect.w / listPtr->elementWidth); + if ( key == A_CURSOR_LEFT || key == A_KP_4 ) + { + if (!listPtr->notselectable) + { + listPtr->cursorPos--; + if (listPtr->cursorPos < 0) + { + listPtr->cursorPos = 0; +#ifdef _XBOX + return qfalse; +#endif + } + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) + { + listPtr->startPos = listPtr->cursorPos - viewmax + 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 + return qfalse; +#endif + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) + { + listPtr->startPos = listPtr->cursorPos - viewmax + 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 + { + viewmax = (item->window.rect.h / listPtr->elementHeight); + if ( key == A_CURSOR_UP || key == A_KP_8 ) + { + if (!listPtr->notselectable) + { + listPtr->cursorPos--; + + if (listPtr->cursorPos < 0) + { + listPtr->cursorPos = 0; +#ifdef _XBOX + return qfalse; +#endif + } + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) + { + listPtr->startPos = listPtr->cursorPos - viewmax + 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_DOWN || key == A_KP_2 ) + { + if (!listPtr->notselectable) + { + listPtr->cursorPos++; + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= count) + { + listPtr->cursorPos = count-1; +#ifdef _XBOX + return qfalse; +#endif + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) + { + listPtr->startPos = listPtr->cursorPos - viewmax + 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 + + +//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; + } + } + // mouse hit + if (key == A_MOUSE1 || key == A_MOUSE2) + { + 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; + } + if ( key == A_HOME || key == A_KP_7) + { + // home + listPtr->startPos = 0; + return qtrue; + } + if ( key == A_END || key == A_KP_1) + { + // end + listPtr->startPos = max; + return qtrue; + } + if (key == A_PAGE_UP || key == A_KP_9 ) + { + // page up + if (!listPtr->notselectable) + { + listPtr->cursorPos -= viewmax; + if (listPtr->cursorPos < 0) + { + listPtr->cursorPos = 0; + } + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) + { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + } + else + { + listPtr->startPos -= viewmax; + if (listPtr->startPos < 0) + { + listPtr->startPos = 0; + } + } + return qtrue; + } + if ( key == A_PAGE_DOWN || key == A_KP_3 ) + { + // page down + if (!listPtr->notselectable) + { + listPtr->cursorPos += viewmax; + if (listPtr->cursorPos < listPtr->startPos) + { + listPtr->startPos = listPtr->cursorPos; + } + if (listPtr->cursorPos >= count) + { + listPtr->cursorPos = count-1; + } + if (listPtr->cursorPos >= listPtr->startPos + viewmax) + { + listPtr->startPos = listPtr->cursorPos - viewmax + 1; + } + item->cursorPos = listPtr->cursorPos; + DC->feederSelection(item->special, item->cursorPos, item); + } + else + { + listPtr->startPos += viewmax; + if (listPtr->startPos > max) + { + listPtr->startPos = max; + } + } + return qtrue; + } + } + 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_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) +{ +//JLFSELECTIONRightLeft +#ifdef _XBOX + 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; + } + } + + +#else + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_AUTOWRAPPED) + + + { + if (key == A_MOUSE1 || key == A_ENTER || key == A_MOUSE2 || key == A_MOUSE3) + { + if (key == A_MOUSE2) + { + item->cursorPos--; + if ( item->cursorPos < 0 ) + { + item->cursorPos = 0; + } + } + else if (item->special) + { + item->cursorPos++; + if ( item->cursorPos >= item->value ) + { + item->cursorPos = 0; + } + } + return qtrue; + } + } +#endif + 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 + return qtrue; + } + } + else if ( key == A_CURSOR_LEFT) + { + if (Item_HandleSelectionPrev(item)) + { + //Item processed it + return qtrue; + } + } + +#endif + + return qtrue; + } + } + } + return qfalse; +} + +/* +================= +Item_Slider_HandleKey +================= +*/ +qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) +{ + //DC->Print("slider handle key\n"); +//JLF MPMOVED +#ifndef _XBOX + float x, value, width, work; + + if (item->window.flags & WINDOW_HASFOCUS && item->cvar && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) + { + + if (key == A_MOUSE1 || key == A_ENTER || key == A_MOUSE2 || key == A_MOUSE3) + { + editFieldDef_t *editDef = (editFieldDef_s *) item->typeData; + if (editDef) + { + rectDef_t testRect; + width = SLIDER_WIDTH; + if (item->text) + { + x = item->textRect.x + item->textRect.w + 8; + } + else + { + x = item->window.rect.x; + } + + testRect = item->window.rect; + testRect.x = x; + value = (float)SLIDER_THUMB_WIDTH / 2; + testRect.x -= value; + //DC->Print("slider x: %f\n", testRect.x); + testRect.w = (SLIDER_WIDTH + (float)SLIDER_THUMB_WIDTH / 2); + //DC->Print("slider w: %f\n", testRect.w); + if (Rect_ContainsPoint(&testRect, DC->cursorx, DC->cursory)) + { + work = DC->cursorx - x; + value = work / width; + value *= (editDef->maxVal - editDef->minVal); + // vm fuckage + // value = (((float)(DC->cursorx - x)/ SLIDER_WIDTH) * (editDef->maxVal - editDef->minVal)); + value += editDef->minVal; + DC->setCVar(item->cvar, va("%f", value)); + return qtrue; + } + } + } + } +#else //_XBOX + 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; + } + } + } +#endif + + + //DC->Print("slider handle key exit\n"); + 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_HandleAccept + If Item has an accept script, run it. +------------------------------------------- +*/ +qboolean Item_HandleAccept(itemDef_t * item) +{ + if (item->accept) + { + Item_RunScript(item, item->accept); + 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; + } + + //JLFMOUSE MPMOVED + // see if the mouse is within the window bounds and if so is this a mouse click +#ifndef _XBOX + if (down && !(menu->window.flags & WINDOW_POPUP) && !Rect_ContainsPoint(&menu->window.rect, DC->cursorx, DC->cursory)) +#else + if (down) +#endif + { + static qboolean inHandleKey = qfalse; + if (!inHandleKey && key == A_MOUSE1 || key == A_MOUSE2 || key == A_MOUSE3) + { + inHandleKey = qtrue; + Menus_HandleOOBClick(menu, key, down); + inHandleKey = qfalse; + 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 (item != NULL) + { + if (Item_HandleKey(item, key, down)) +//JLFLISTBOX + { +#ifdef _XBOX + if (key == A_MOUSE1 || key == A_MOUSE2 ||(item->type == ITEM_TYPE_MULTI && (key == A_CURSOR_RIGHT || key == A_CURSOR_LEFT))) +#endif + + Item_Action(item); + inHandler = qfalse; + return; + } + + } + + if (!down) + { + 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_F11: + if (DC->getCVarValue("developer")) + { + uis.debugMode ^= 1; + } + break; + + case A_F12: + if (DC->getCVarValue("developer")) + { + DC->executeText(EXEC_APPEND, "screenshot\n"); + } + break; + case A_KP_8: + case A_CURSOR_UP: + Menu_SetPrevCursorItem(menu); + break; + + + case A_ESCAPE: + if (!g_waitingForKey && menu->onESC) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->onESC); + } + break; + case A_TAB: + case A_KP_2: + case A_CURSOR_DOWN: + Menu_SetNextCursorItem(menu); + break; + + case A_MOUSE1: + case A_MOUSE2: + if (item) + { + if (item->type == ITEM_TYPE_TEXT) + { +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) +#endif + { + if ( item->action ) + Item_Action(item); + else + { + if (menu->onAccept) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->onAccept); + } + } + + } + } + else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) + { +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) +#endif + { + item->cursorPos = 0; + g_editingField = qtrue; + g_editItem = item; + DC->setOverstrikeMode(qtrue); + } + } +//JLFACCEPT +// add new types here as needed +/* Notes: + Most controls will use the dpad to move through the selection possibilies. Buttons are the only exception. + Buttons will be assumed to all be on one menu together. If the start or A button is pressed on a control focus, that + means that the menu is accepted and move onto the next menu. If the start or A button is pressed on a button focus it + should just process the action and not support the accept functionality. +*/ +//JLFACCEPT + else if ( item->type == ITEM_TYPE_MULTI || item->type == ITEM_TYPE_YESNO || item->type == ITEM_TYPE_SLIDER) + { + + if (Item_HandleAccept(item)) + { + //Item processed it overriding the menu processing + return; + } + else if (menu->onAccept) + { + itemDef_t it; + it.parent = menu; + Item_RunScript(&it, menu->onAccept); + } + + + } + +//END JLFACCEPT + else + { +//JLFMOUSE +#ifndef _XBOX + if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) +#endif + { + Item_Action(item); + } + } + } + else 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) + { + if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) + { + item->cursorPos = 0; + g_editingField = qtrue; + g_editItem = item; + DC->setOverstrikeMode(qtrue); + } + else + { + Item_Action(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", + "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" ); + +// g_demoStartFade = 0; +// g_demoStartTransition = 0; +} + +const char *attractMovieNames[] = { + "jk1", + "jk2", + "jk3", + "jk4", + "jk5", +}; + +const int numAttractMovies = sizeof(attractMovieNames) / sizeof(attractMovieNames[0]); +static int curAttractMovie = 0; + +void G_DemoFrame() +{ + bool keypressed = false; + if (g_runningDemo) + { + while (!keypressed) + keypressed = CIN_PlayAllFrames( "atvi.bik", 0, 0, 640, 480, 0, true ); + G_DemoEnd(); +// if (!g_demoStartTransition && level->time > g_demoLastChange - 750) +// { +// g_demoTransitionType = rand() % 8; +// g_demoStartTransition = level->time + 750; +// if (level->time < 5000) +// { +// g_demoStartTransition = -g_demoStartTransition; +// g_demoTransitionType = 0; +// } +// } + +// if (g_demoLastChange < level->time) +// { +// int nEnt = (rand() % 7) + 1; +// if (nEnt == g_entities[0].client->ps.viewEntity) +// g_entities[0].client->ps.viewEntity = ((nEnt + 1) % 7) + 1; +// else +// g_entities[0].client->ps.viewEntity = nEnt; +// g_demoLastChange = level->time + Q_irand(g_demoTimeBeforeChangePlayer - 3000, g_demoTimeBeforeChangePlayer + 3000); +// } + +// if (!g_demoStartFade && level->time > g_demoTimeBeforeStop-3000) +// { +// g_demoStartFade = level->time + 2000; +// } + +// if (level->time > g_demoTimeBeforeStop) +// { +// G_DemoEnd(); +// return; +// } + + + } + else + { + 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()) + G_DemoStart(); + } + else + { + g_demoLastKeypress = Sys_Milliseconds(); + } + } +} + +void G_DemoKeypress() +{ + g_demoLastKeypress = Sys_Milliseconds(); + +//JLF moved +// g_demoLastKeypress = Sys_Milliseconds(); + +} + + +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" ); + +// g_demoStartFade = 0; +// g_demoStartTransition = 0; +// g_demoLastKeypress = 0; +} + +void PlayDemo() +{ +// bool keypressed = false; + G_DemoStart(); + CIN_PlayAllFrames( attractMovieNames[curAttractMovie], 0, 0, 640, 480, 0, true ); + curAttractMovie = (curAttractMovie + 1) % numAttractMovies; +// 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..3fe7ba1 --- /dev/null +++ b/code/ui/ui_shared.h @@ -0,0 +1,528 @@ +#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; +} 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 64*1024 + +#else +#ifdef CGAME +#define STRING_POOL_SIZE 128*1024 +#else +#define STRING_POOL_SIZE 384*1024 +#endif +#endif + +#define NUM_CROSSHAIRS 9 + +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 150 +#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 *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); +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..0731041 --- /dev/null +++ b/code/ui/ui_splash.cpp @@ -0,0 +1,290 @@ + +#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" + +#define SP_TextureExt "dds" + +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); + 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(); + 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 = 320 - width / 2; + float x2 = 320 + width / 2; + float y1 = 240 - height / 2; + float y2 = 240 + height / 2; + + 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"; + 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." SP_TextureExt,name,ext); + buffer = SP_LoadFile(fullname); + + if (!buffer) + { + sprintf(fullname, "%s." SP_TextureExt, 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; + license = SP_LoadFileWithLanguage("d:\\base\\media\\LicenseScreen"); + + if (license) + { + + for (int c = 0; c < 3; ++c) + { + SP_DrawTexture(license, 1024, 1024, -20.0); + } + Z_Free(license); + } + + SP_LicenseDone = true; +} + diff --git a/code/ui/ui_splash.h b/code/ui/ui_splash.h new file mode 100644 index 0000000..f27df31 --- /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..38b2383 --- /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/ui/vssver.scc b/code/ui/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..4cc024e5f392e4862b2498b26f2c07fd3d1531ad GIT binary patch literal 288 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiY4N`>>4urYu^DUdEKwPZIhULeN^6e$Dpi(BKL zzkjm_%r6J>XFksC(fsWp2hvgjBogO@q}VqFg5|4#{14d&MEEzR$w>g^tAYG8p0}Dk z=N8E^1Nk*Te!JHyQA4f@u=-jc|LpuVy6dC)<@kW|bwK{Vm+u<_b(G|Kfc#lNexv^5 z{iXACvAD${^0{L@*{GTf8s(O{KC@2B>bAf!f@CT`HH+w0t0Qsv0 s85p){MaFzl7k4Jc3A1(sI@@)fr|c)57_J_V3}0UI$*K>z>% literal 0 HcmV?d00001 diff --git a/code/unix/Makefile b/code/unix/Makefile new file mode 100644 index 0000000..5c8dda8 --- /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..8bfe671 --- /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..3dac0a7 --- /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..77c816a --- /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..73174ee --- /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..a50e1f6 --- /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..c31b6b8 --- /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..d6c2945 --- /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..cc29312 --- /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..e1a3cce --- /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..41a02f1 --- /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..d2d415f --- /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..72806f2 --- /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..558d7d9 --- /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/unix/vssver.scc b/code/unix/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..19a43728e3efda1acd19d0422807f9d5ac68b47c GIT binary patch literal 272 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiZ3)_7JnkBtEg<^ySCtr@=$d!{Qe0!0=8`A-GZ z%{zNo!2E?k{*I%2wb}1TgZYbq{8xw7cf9vf1oIaI`CQVRa?K{jVEz&y-_$9Db>e*! zFn=kK@2u>q!=Haz8D#!4Am1%9;G3-388Cl2kY5rexUF^1Z7_cYkbhC_)L+8~_rUy> zK>i20L+tAf--7w8fc!tVk|*7+`T*vy2J)j*?j3f1{2$C;1LV(flsl-M(F*3T1@dn_ M{C=vmWFnXk08N@o(f|Me literal 0 HcmV?d00001 diff --git a/code/update_spents.bat b/code/update_spents.bat new file mode 100644 index 0000000..6f36921 --- /dev/null +++ b/code/update_spents.bat @@ -0,0 +1 @@ +k:\util\EntityDefMaker w:\bin\gamesource\*.cpp w:\game\base\scripts\sp_entities.def diff --git a/code/vssver.scc b/code/vssver.scc new file mode 100644 index 0000000000000000000000000000000000000000..82795906500ab58b91365ec2331f1813e623b305 GIT binary patch literal 240 zcmXpJVq_>gDwNv&Y=_H|yDBkqiQiW|Xx=BJ$ie^yDnRT?{{K5p}*RnD&^m^IM?mBk-J4pU9kpH!O{`5Pw&EJ`U{3k&ECY`w7AM|^{@Q`=WSA^+{LT)PF9!0rdwkwGN5lU+kjd~)fPvw8MPT4Qp^e|=fcy_Yer4w853jFeel 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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